From b2617039faa22a61e21d4c1c42e9ef98ad47e5a9 Mon Sep 17 00:00:00 2001 From: miracle Date: Wed, 17 Jun 2026 10:33:10 +0800 Subject: [PATCH] fix(mysql): normalize smart quotes in JSON input * fix(grid): normalize smart quotes in JSON input to prevent parse errors When manually typing JSON values in the data grid, input methods (especially Chinese IME and macOS smart punctuation) can automatically convert standard ASCII quotes to smart quotes (U+201C, U+201D, etc.), causing JSON parse failures. Changes: - Remove incorrect logic that tried to parse original value before normalization - Add hasSmartQuotes() function to detect 7 types of smart quotes: * Chinese quotes: U+201C, U+201D * German quotes: U+201E, U+201F * Smart single quotes: U+2018, U+2019 * Fullwidth quote: U+FF02 - Rewrite normalizeSmartQuotes() using charCodeAt to avoid oxc compiler issues - Add comprehensive tests for MySQL JSON field quote normalization All existing tests remain passing. * fix(grid): preserve valid JSON while normalizing smart quotes --- apps/desktop/src/lib/dataGridCellCoercion.ts | 33 ++++++++++- .../dataGridCellCoercion.mysql-json.test.ts | 59 +++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 packages/app-tests/dataGridCellCoercion.mysql-json.test.ts 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); +});