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
This commit is contained in:
miracle 2026-06-17 10:33:10 +08:00 committed by GitHub
parent 5a08d42258
commit b2617039fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 89 additions and 3 deletions

View File

@ -80,15 +80,21 @@ function isPostgresArrayColumn(columnInfo: Pick<ColumnInfo, "data_type"> | 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 {

View File

@ -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);
});