diff --git a/package-lock.json b/package-lock.json index b374df4b..463ba9ad 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 9191ab6f..d55464b3 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 434c8bb6..eae8eed3 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 00000000..852ca8d3 --- /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 f147e9e7..cf150ad2 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 00000000..06d4263c --- /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: [], + }, + ], + }, +]);