From 07fbc1eda0373f75fd8457b948554751d55b0e9b Mon Sep 17 00:00:00 2001 From: maojunxyz Date: Fri, 8 Aug 2025 17:49:43 +0800 Subject: [PATCH 1/3] feat: Add JSON Repair operation with comprehensive repair capabilities - Add new JSON Repair operation using jsonrepair library (v3.13.0) - Support for fixing common JSON issues: * Missing quotes around object keys * Single quotes to double quotes conversion * Trailing commas removal * Missing commas insertion * Python constants (True/False/None) to JSON equivalents * Comments removal (both // and /* */ styles) * JSONP notation handling - Include comprehensive test suite with 11 test cases - Add operation to 'Code tidy' category - Follows KISS principle with minimal configuration - Uses dynamic import for robust module loading Resolves the common issue of repairing malformed JSON data, particularly useful for processing JSON with missing commas between object properties. Author: maojunxyz --- package-lock.json | 10 ++ package.json | 1 + src/core/config/Categories.json | 1 + src/core/operations/JSONRepair.mjs | 48 ++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/JSONRepair.mjs | 164 ++++++++++++++++++++++++++ 6 files changed, 225 insertions(+) create mode 100644 src/core/operations/JSONRepair.mjs create mode 100644 tests/operations/tests/JSONRepair.mjs diff --git a/package-lock.json b/package-lock.json index b374df4bc9..463ba9ade9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "json5": "^2.2.3", "jsonata": "^2.0.3", "jsonpath-plus": "^9.0.0", + "jsonrepair": "^3.13.0", "jsonwebtoken": "8.5.1", "jsqr": "^1.4.0", "jsrsasign": "^11.1.0", @@ -12520,6 +12521,15 @@ "node": ">=14.0.0" } }, + "node_modules/jsonrepair": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.13.0.tgz", + "integrity": "sha512-5YRzlAQ7tuzV1nAJu3LvDlrKtBFIALHN2+a+I1MGJCt3ldRDBF/bZuvIPzae8Epot6KBXd0awRZZcuoeAsZ/mw==", + "license": "ISC", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, "node_modules/jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", diff --git a/package.json b/package.json index 9191ab6f03..d55464b381 100644 --- a/package.json +++ b/package.json @@ -144,6 +144,7 @@ "json5": "^2.2.3", "jsonata": "^2.0.3", "jsonpath-plus": "^9.0.0", + "jsonrepair": "^3.13.0", "jsonwebtoken": "8.5.1", "jsqr": "^1.4.0", "jsrsasign": "^11.1.0", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 434c8bb619..eae8eed3e8 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -465,6 +465,7 @@ "JavaScript Minify", "JSON Beautify", "JSON Minify", + "JSON Repair", "XML Beautify", "XML Minify", "SQL Beautify", diff --git a/src/core/operations/JSONRepair.mjs b/src/core/operations/JSONRepair.mjs new file mode 100644 index 0000000000..852ca8d312 --- /dev/null +++ b/src/core/operations/JSONRepair.mjs @@ -0,0 +1,48 @@ +/** + * @author maojunxyz [maojun@linux.com] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; +import Operation from "../Operation.mjs"; + +/** + * JSON Repair operation + */ +class JSONRepair extends Operation { + + /** + * JSONRepair constructor + */ + constructor() { + super(); + + this.name = "JSON Repair"; + this.module = "Code"; + this.description = "Attempts to repair invalid JSON by fixing common issues such as missing quotes around keys, trailing commas, single quotes, missing brackets, and more.

This operation can fix:
• Missing quotes around keys
• Single quotes instead of double quotes
• Trailing commas
• Missing commas
• Missing closing brackets
• Python constants (None, True, False)
• Comments
• JSONP notation
• And many other common JSON formatting issues

Uses the jsonrepair library for comprehensive JSON repair.

Tags: json fix, json repair, json validate, json format"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + if (!input) return ""; + + try { + // Dynamic import of jsonrepair to handle potential module loading issues + const { jsonrepair } = await import("jsonrepair"); + return jsonrepair(input); + + } catch (err) { + throw new OperationError("Unable to repair JSON. The input contains errors that cannot be automatically fixed.\n" + err.message); + } + } +} + +export default JSONRepair; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index f147e9e7c7..cf150ad28a 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -93,6 +93,7 @@ import "./tests/JA3SFingerprint.mjs"; import "./tests/Jsonata.mjs"; import "./tests/JSONBeautify.mjs"; import "./tests/JSONMinify.mjs"; +import "./tests/JSONRepair.mjs"; import "./tests/JSONtoCSV.mjs"; import "./tests/Jump.mjs"; import "./tests/JWK.mjs"; diff --git a/tests/operations/tests/JSONRepair.mjs b/tests/operations/tests/JSONRepair.mjs new file mode 100644 index 0000000000..06d4263c41 --- /dev/null +++ b/tests/operations/tests/JSONRepair.mjs @@ -0,0 +1,164 @@ +/** + * JSONRepair tests. + * + * @author maojunxyz [maojun@linux.com] + * + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "JSON Repair: empty string", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: valid JSON unchanged", + input: '{"name": "John", "age": 30}', + expectedOutput: '{"name": "John", "age": 30}', + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: missing quotes around keys", + input: '{name: "John", age: 30}', + expectedOutput: '{"name": "John", "age": 30}', + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: single quotes to double quotes", + input: "{'name': 'John', 'age': 30}", + expectedOutput: '{"name": "John", "age": 30}', + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: trailing comma in object", + input: '{"name": "John", "age": 30,}', + expectedOutput: '{"name": "John", "age": 30}', + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: trailing comma in array", + input: '[1, 2, 3,]', + expectedOutput: '[1, 2, 3]', + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: Python constants", + input: '{"active": True, "data": None, "flag": False}', + expectedOutput: '{"active": true, "data": null, "flag": false}', + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: line comments", + input: `{ + "name": "John", // This is a comment + "age": 30 +}`, + expectedOutput: `{ + "name": "John", + "age": 30 +}`, + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: block comments", + input: `{ + "name": "John", /* This is a + multi-line comment */ + "age": 30 +}`, + expectedOutput: `{ + "name": "John", + "age": 30 +}`, + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: missing comma", + input: `{ + "name": "John" + "age": 30, + "city": "Boston" +}`, + expectedOutput: `{ + "name": "John", + "age": 30, + "city": "Boston" +}`, + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, + { + name: "JSON Repair: complex mixed issues", + input: `{ + name: 'John', // Person's name + age: 30, + active: True, + data: None, +}`, + expectedOutput: `{ + "name": "John", + "age": 30, + "active": true, + "data": null +}`, + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, +]); From dd21b72193174a9cdde56839f978712eb34b3b95 Mon Sep 17 00:00:00 2001 From: maojunxyz Date: Fri, 8 Aug 2025 18:04:36 +0800 Subject: [PATCH 2/3] test: Add JSONP notation test case for JSON Repair operation - Add test case to verify JSONP callback wrapper removal - Validates that callback({name: John, age: 30}) becomes {name: John, age: 30} - Ensures comprehensive coverage of jsonrepair library features - Total test cases now: 12 (previously 11) --- tests/operations/tests/JSONRepair.mjs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/operations/tests/JSONRepair.mjs b/tests/operations/tests/JSONRepair.mjs index 06d4263c41..46768df9bc 100644 --- a/tests/operations/tests/JSONRepair.mjs +++ b/tests/operations/tests/JSONRepair.mjs @@ -161,4 +161,15 @@ TestRegister.addTests([ }, ], }, + { + name: "JSON Repair: JSONP notation", + input: 'callback({"name": "John", "age": 30})', + expectedOutput: '{"name": "John", "age": 30}', + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, ]); From 177791ae5d03fa80f2dcc433dfa86bd2d95b688f Mon Sep 17 00:00:00 2001 From: maojunxyz Date: Fri, 8 Aug 2025 18:27:13 +0800 Subject: [PATCH 3/3] fix: Fix ESLint errors in JSONRepair files - Fix trailing spaces in JSONRepair.mjs - Convert single quotes to double quotes in test files - Fix line ending style (CRLF to LF) - All lint checks now pass --- src/core/operations/JSONRepair.mjs | 2 +- tests/operations/tests/JSONRepair.mjs | 28 +++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/core/operations/JSONRepair.mjs b/src/core/operations/JSONRepair.mjs index 852ca8d312..65eacdf702 100644 --- a/src/core/operations/JSONRepair.mjs +++ b/src/core/operations/JSONRepair.mjs @@ -38,7 +38,7 @@ class JSONRepair extends Operation { // Dynamic import of jsonrepair to handle potential module loading issues const { jsonrepair } = await import("jsonrepair"); return jsonrepair(input); - + } catch (err) { throw new OperationError("Unable to repair JSON. The input contains errors that cannot be automatically fixed.\n" + err.message); } diff --git a/tests/operations/tests/JSONRepair.mjs b/tests/operations/tests/JSONRepair.mjs index 46768df9bc..9b88bad00f 100644 --- a/tests/operations/tests/JSONRepair.mjs +++ b/tests/operations/tests/JSONRepair.mjs @@ -22,8 +22,8 @@ TestRegister.addTests([ }, { name: "JSON Repair: valid JSON unchanged", - input: '{"name": "John", "age": 30}', - expectedOutput: '{"name": "John", "age": 30}', + input: "{\"name\": \"John\", \"age\": 30}", + expectedOutput: "{\"name\": \"John\", \"age\": 30}", recipeConfig: [ { op: "JSON Repair", @@ -33,8 +33,8 @@ TestRegister.addTests([ }, { name: "JSON Repair: missing quotes around keys", - input: '{name: "John", age: 30}', - expectedOutput: '{"name": "John", "age": 30}', + input: "{name: \"John\", age: 30}", + expectedOutput: "{\"name\": \"John\", \"age\": 30}", recipeConfig: [ { op: "JSON Repair", @@ -45,7 +45,7 @@ TestRegister.addTests([ { name: "JSON Repair: single quotes to double quotes", input: "{'name': 'John', 'age': 30}", - expectedOutput: '{"name": "John", "age": 30}', + expectedOutput: "{\"name\": \"John\", \"age\": 30}", recipeConfig: [ { op: "JSON Repair", @@ -55,8 +55,8 @@ TestRegister.addTests([ }, { name: "JSON Repair: trailing comma in object", - input: '{"name": "John", "age": 30,}', - expectedOutput: '{"name": "John", "age": 30}', + input: "{\"name\": \"John\", \"age\": 30,}", + expectedOutput: "{\"name\": \"John\", \"age\": 30}", recipeConfig: [ { op: "JSON Repair", @@ -66,8 +66,8 @@ TestRegister.addTests([ }, { name: "JSON Repair: trailing comma in array", - input: '[1, 2, 3,]', - expectedOutput: '[1, 2, 3]', + input: "[1, 2, 3,]", + expectedOutput: "[1, 2, 3]", recipeConfig: [ { op: "JSON Repair", @@ -77,8 +77,8 @@ TestRegister.addTests([ }, { name: "JSON Repair: Python constants", - input: '{"active": True, "data": None, "flag": False}', - expectedOutput: '{"active": true, "data": null, "flag": false}', + input: "{\"active\": True, \"data\": None, \"flag\": False}", + expectedOutput: "{\"active\": true, \"data\": null, \"flag\": false}", recipeConfig: [ { op: "JSON Repair", @@ -143,7 +143,7 @@ TestRegister.addTests([ { name: "JSON Repair: complex mixed issues", input: `{ - name: 'John', // Person's name + name: "John", // Person's name age: 30, active: True, data: None, @@ -163,8 +163,8 @@ TestRegister.addTests([ }, { name: "JSON Repair: JSONP notation", - input: 'callback({"name": "John", "age": 30})', - expectedOutput: '{"name": "John", "age": 30}', + input: "callback({\"name\": \"John\", \"age\": 30})", + expectedOutput: "{\"name\": \"John\", \"age\": 30}", recipeConfig: [ { op: "JSON Repair",