diff --git a/apps/desktop/src/lib/dataGridCellCoercion.ts b/apps/desktop/src/lib/dataGridCellCoercion.ts index e89d2cd38..34165c237 100644 --- a/apps/desktop/src/lib/dataGridCellCoercion.ts +++ b/apps/desktop/src/lib/dataGridCellCoercion.ts @@ -80,15 +80,21 @@ function isPostgresArrayColumn(columnInfo: Pick | undef } function normalizeSmartQuotedJsonInput(value: string): string { - if (!/[“”]/.test(value)) return value; + // Check for smart double quotes that input methods might insert. + // U+201C, U+201D, U+201E, U+201F, U+FF02 + if (!hasSmartDoubleQuotes(value)) return value; const trimmed = value.trim(); if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return value; + try { JSON.parse(value); return value; } catch { - // macOS smart punctuation can turn JSON delimiters into Chinese-style quotes. + // Input methods can turn JSON delimiters into smart quotes. } + + // Input methods (especially on macOS and with Chinese IME) can turn JSON delimiters + // into smart quotes. Normalize them to standard ASCII quotes. const normalized = normalizeSmartQuotes(value); try { JSON.parse(normalized); @@ -98,8 +104,29 @@ function normalizeSmartQuotedJsonInput(value: string): string { } } +function hasSmartDoubleQuotes(value: string): boolean { + // Check for smart double quotes: U+201C, U+201D, U+201E, U+201F, U+FF02 + for (let i = 0; i < value.length; i++) { + const code = value.charCodeAt(i); + if (code === 0x201c || code === 0x201d || code === 0x201e || code === 0x201f || code === 0xff02) { + return true; + } + } + return false; +} + function normalizeSmartQuotes(value: string): string { - return value.replace(/[“”]/g, '"'); + let result = ""; + for (let i = 0; i < value.length; i++) { + const code = value.charCodeAt(i); + if (code === 0x201c || code === 0x201d || code === 0x201e || code === 0x201f || code === 0xff02) { + // Convert to standard double quote + result += '"'; + } else { + result += value[i]; + } + } + return result; } function formatPostgresArrayText(value: unknown[]): string { diff --git a/packages/app-tests/dataGridCellCoercion.mysql-json.test.ts b/packages/app-tests/dataGridCellCoercion.mysql-json.test.ts new file mode 100644 index 000000000..d896c83eb --- /dev/null +++ b/packages/app-tests/dataGridCellCoercion.mysql-json.test.ts @@ -0,0 +1,59 @@ +import { strict as assert } from "node:assert"; +import { test } from "vitest"; +import { coerceDataGridCellValue } from "../../apps/desktop/src/lib/dataGridCellCoercion.ts"; + +test("MySQL JSON field with English quotes should remain unchanged", () => { + const input = '{"2:3":"3:4","3:2":"4:3","21:9":"16:9"}'; + const result = coerceDataGridCellValue({ + value: input, + oldValue: null, + databaseType: "mysql", + columnInfo: { data_type: "json" }, + }); + + assert.equal(result, input); + assert.ok(result.includes('"'), "Should contain English quotes"); + assert.ok(!result.includes('“') && !result.includes('”'), "Should NOT contain Chinese quotes"); +}); + +test("MySQL JSON field with Chinese quotes should be normalized to English quotes", () => { + const input = "{\u201c2:3\u201d:\u201c3:4\u201d,\u201c3:2\u201d:\u201c4:3\u201d,\u201c21:9\u201d:\u201c16:9\u201d}"; + const expected = '{"2:3":"3:4","3:2":"4:3","21:9":"16:9"}'; + + const result = coerceDataGridCellValue({ + value: input, + oldValue: null, + databaseType: "mysql", + columnInfo: { data_type: "json" }, + }); + + assert.equal(result, expected); + assert.ok(result.includes('"'), "Should contain English quotes"); + assert.ok(!result.includes('“') && !result.includes('”'), "Should NOT contain Chinese quotes"); +}); + +test("MySQL JSON field with mixed quotes should be normalized", () => { + const input = "{\u201ckey\u201d:\"value\"}"; + const expected = '{"key":"value"}'; + + const result = coerceDataGridCellValue({ + value: input, + oldValue: null, + databaseType: "mysql", + columnInfo: { data_type: "json" }, + }); + + assert.equal(result, expected); +}); + +test("MySQL JSON field with smart apostrophe inside a string should remain unchanged", () => { + const input = '{"text":"it\u2019s ok"}'; + const result = coerceDataGridCellValue({ + value: input, + oldValue: null, + databaseType: "mysql", + columnInfo: { data_type: "json" }, + }); + + assert.equal(result, input); +});