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..65eacdf702 --- /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..9b88bad00f --- /dev/null +++ b/tests/operations/tests/JSONRepair.mjs @@ -0,0 +1,175 @@ +/** + * 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: [], + }, + ], + }, + { + name: "JSON Repair: JSONP notation", + input: "callback({\"name\": \"John\", \"age\": 30})", + expectedOutput: "{\"name\": \"John\", \"age\": 30}", + recipeConfig: [ + { + op: "JSON Repair", + args: [], + }, + ], + }, +]);