json-repair-and-schema
Skill4 starsChanged 20 days agoMITView on GitHub
Validate, repair and schema-check JSON and JSONL with explicit, line-accurate diagnostics. Use this skill whenever an agent hands back a JSON or JSONL payload that may be malformed (trailing commas, single quotes, unquoted keys, Python literals, truncated streams), whenever you need to prove a document matches a JSON Schema before you trust it, when a model response arrives as broken JSON on stdin, or when you need to find which line of a large JSONL file is bad without loading the whole file into memory. Repair is best-effort and can silently invent data, so this skill always reports before/after hashes and proves the repaired text parses. It does not generate schemas, infer schemas from examples, fetch remote $ref targets, enforce `format` keywords, coerce types, or guarantee that repaired data is correct rather than merely parseable.
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 3 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 38
Use validate when the source should remain untouched and you need to know whether it parses or conforms to a schema.
- Very specific: line 39
Use repair when malformed JSON is blocking a workflow and you want a best-effort parseable candidate plus before/after hashes.
- Very specific: line 40
Use jsonl to locate invalid records in a line-delimited stream.
- Distinctive: line 58
Keep the original text unchanged and choose a distinct output path.
- Very specific: line 59
Run repair with verification enabled (the default).
- Very specific: line 60
Run validate on the repaired file, preferably with the intended schema.
- Distinctive: line 62
Only replace an authoritative file after the user has asked for that change and the repaired content has been reviewed.
- Very specific: line 88
No input file is modified.
- Distinctive: line 179
Check a model response against a schema and read the failure as a sentence:
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
json-repair-and-schema
Three commands for the boundary between text a model produced and JSON a program can consume: validate (does it parse, does it match), repair (best-effort make it parse, prove it, hash it), jsonl (which line is broken).
node scripts/index.js validate response.json schema.json
node scripts/index.js repair broken.json fixed.json
node scripts/index.js jsonl events.jsonl --schema event.schema.jsonWhen to use
- A model, tool, or log produced text that is almost JSON: single quotes, unquoted keys, trailing commas,
True/False/None,//comments, a truncated object. - You need to know whether a document matches a JSON Schema before you act on it, and you need errors with a JSON pointer path, not just "invalid".
- You need to know which line of a JSONL file is broken, on a file too large to hold in memory.
- You want to repair a schema folder that uses
"$ref": "./common.json#/$defs/pos"and have the relative reference resolve. - You need to prove a repair actually changed something: byte counts and sha256 of the input and the output.
- The payload is arriving on stdin and you do not want to write it to disk first.
When not to use
- You need to produce a schema or infer one from examples: write the schema yourself or use a different tool.
- You need
formatassertions (email,date-time,uri, ...). Ajv treatsformatas an annotation here becauseajv-formatsis not a dependency; a document with"email": "nope"passes. - You need to fetch a remote
$refover HTTP. Only schemas that sit next to the schema file (or are already on disk) resolve; this skill never touches the network. - You need YAML, TOML, XML, CSV or protobuf: convert first.
- You need type coercion (
"36"to36) or default filling. A schema tells you a value is wrong; fixing it is your job. - You need the repaired data to be correct. Repair proves the text parses. That is all it proves.
- You need draft 2019-09/2020-12 features Ajv does not implement (
unevaluatedProperties, dynamic refs across files, vocabularies). Draft-07 works.
Choose the command
- Use
validatewhen the source should remain untouched and you need to know whether it parses or conforms to a schema. - Use
repairwhen malformed JSON is blocking a workflow and you want a best-effort parseable candidate plus before/after hashes. Review repaired values against the source; parseability does not establish intended meaning. - Use
jsonlto locate invalid records in a line-delimited stream. Add a schema when each line must satisfy a known contract, and use repair output only when you intend to inspect and retain repaired lines.
For model or tool output, treat the payload as untrusted input. Validate it before using it to drive commands, database changes, or user-visible decisions. Keep the original available so any semantic change introduced during repair can be detected and reviewed.
Detailed validation and repair procedure
Identify the data shape
Decide whether the input is one JSON document or JSON Lines. A normal JSON document has one root value; a JSONL file has one JSON value per non-empty physical line. Do not send a JSON array to jsonl just because it contains many records, or send JSONL to validate and expect per-record diagnostics. For stdin, use - as the input and keep the tool's one-line JSON result separate from the data stream.
Validate without changing input
Run validate first when you have a schema or need a syntax-only check. Read valid, schema_checked, error_count, and the error paths together. Without a schema, valid: true means only that the input parses. With a schema, use instancePath to locate the data and schemaPath/keyword to understand the contract. Fix source data or the schema based on intent; do not remove a useful constraint merely to make a payload pass.
For a $ref failure, check that referenced schemas are valid .json files in the schema's directory and that references use the supported local scope. Do not expect HTTP refs, parent-directory traversal, or every modern JSON Schema vocabulary to work.
Repair as a reviewed transformation
- Keep the original text unchanged and choose a distinct output path.
- Run
repairwith verification enabled (the default). Inspect whether the document changed, the listed defect classes, before/after hashes, andvalid_after_repair. - Run
validateon the repaired file, preferably with the intended schema. - Compare important fields and values to the source or another trusted record. Missing quotes, braces, commas, or values may be supplied heuristically.
- Only replace an authoritative file after the user has asked for that change and the repaired content has been reviewed.
changed: false means the parsed input was left alone and the named output is not written. Do not mistake that for “the requested output file was produced.” --no-verify removes the parser round-trip check; use it only when raw repair text is specifically needed for manual inspection, never as proof of valid JSON.
Repair JSONL carefully
Use jsonl to locate malformed records and optionally validate each record against a shared schema. Report physical line numbers, remembering that blank lines are skipped for counts but still occupy line positions. Inspect the bounded offending text preview rather than assuming it is the whole record. With --repair-out, valid lines are retained and broken lines that cannot be repaired are skipped; check repaired_lines, unrepairable, invalid, and truncated before treating the output as complete. This can create a lossy stream, so preserve the original and account for dropped records.
Choosing a limit
validate and repair load the whole document, so use the byte cap to keep a large input bounded. jsonl streams records but still retains the configured error list and has line/error limits. If any limit truncates the scan, describe the result as a partial inspection. A low error_count in a truncated scan is not evidence that the remaining file is valid.
Example incident note
The payload is syntactically invalid at source line 42. I repaired a copy;
the output parses, but it has not yet passed the event schema. The original is
unchanged, and the missing field value needs confirmation from the source system.This distinguishes parse success from schema validity and semantic correctness.
Requirements
- Node.js >= 20.
- Dependencies already installed in the repository:
ajv(JSON Schema, draft-07 by default) andjsonrepair(best-effort text repair). Nothing is downloaded at run time; this skill makes no network calls. - No input file is modified.
repair <output.json>andjsonl --repair-outcreate or overwrite exactly one path that you pass in.
Output contract
Success (exit code 0, one line of JSON on stdout, nothing on stderr):
{"data": <object described per command below>}Failure (exit code 1, one line of JSON on stderr, nothing on stdout):
{"error": "<human readable message>"}Nothing else is written to stdout or stderr: no banners, no progress logs, no stack traces. --help prints {"data":{"usage":"..."}} and exits 0. --version prints {"data":{"name":"@unreadlogs/skill-json-repair-and-schema","version":"0.1.0"}} and exits 0. - as the input path reads stdin and is reported as "input": "<stdin>".
validate
{"data":{"input":"response.json","schema":"schema.json","schema_checked":true,
"schema_siblings":3,"valid":false,"errors":[{"instancePath":"/age",
"schemaPath":"#/properties/age/type","keyword":"type","message":"must be integer",
"params":{"type":"integer"},"summary":"/age must be integer"}],
"error_count":1,"summary":"/age must be integer","bytes":25,"bytes_human":"25 B"}}- Without a schema:
schemaisnull,schema_checkedisfalse, andvalidmeans only "this text is valid JSON". The top-levelsummarysays so. - Each error entry is
{instancePath, schemaPath, keyword, message, params, summary}.instancePathis a JSON pointer and is/(not"") for the document root. A parse failure useskeyword: "json-parse"andschemaPath: null. schema_siblingsis the number of sibling*.jsonfiles that were registered so that relative$refcan resolve.
repair
{"data":{"input":"broken.json","output":"fixed.json","written":true,"changed":true,
"repairs":[{"code":"unquoted_key","occurrences":2},{"code":"single_quoted_string","occurrences":2},
{"code":"trailing_comma","occurrences":1}],"valid_after_repair":true,"depth":1,
"before":{"bytes":23,"sha256":"b2fb7476..."},"after":{"bytes":26,"sha256":"23487ee9..."}}}changed: falsemeans the input already parsed.writtenisfalseand no file is created or overwritten, even if you named an output path.repairslists the defect classes jsonrepair most likely fixed, with occurrence counts. It never quotes your data.valid_after_repairis the result of round-tripping the repaired text throughJSON.parse. It isnullunder--no-verify.- With no output path, nothing is written to disk and the repaired document is returned inline as
data.value(never as raw stdout text), so the one-line contract still holds.
jsonl
{"data":{"input":"events.jsonl","schema":null,"schema_checked":false,"repair_out":null,
"lines":3,"parsed":2,"valid":2,"invalid":1,"unrepairable":0,"repaired_lines":0,
"error_count":1,"errors":[{"line":3,"error":"not valid JSON: Unexpected end of JSON input",
"keyword":"json-parse","data":"{\"a\":","data_truncated":false}],
"truncated":false,"truncated_reason":null,"bytes":25,"bytes_human":"25 B"}}linescounts non-empty lines;parsedcounts lines that produced a value;validcounts lines that passed everything;invalidcounts failures;error_countiserrors.length.lineis the true 1-based source line, blank lines included, so it matches what an editor shows.- Each error entry has
error(one human sentence),keyword, an optionalschema_errorsarray, and the offending text asdata(first 200 characters;data_truncatedsays if it was cut). --repair-outwrites valid lines verbatim and broken lines replaced by their repaired text. Lines jsonrepair cannot fix are skipped and counted inunrepairable.
Commands
| Command | Arguments | Output data |
validate | <input|-> [schema.json] [--schema f] [--max-bytes n] | input, schema, schema_checked, schema_siblings, valid, errors, error_count, summary, bytes, bytes_human |
repair | <input|-> [output.json] [--no-verify] [--max-bytes n] [--max-repair-depth n] | input, output, written, changed, repairs, valid_after_repair, depth, before, after, plus value when there is no output path |
jsonl | <input|-> [--schema f] [--repair-out f] [--limit n] [--max-errors n] [--max-bytes n] | input, schema, schema_checked, repair_out, lines, parsed, valid, invalid, unrepairable, repaired_lines, error_count, errors, truncated, truncated_reason, bytes, bytes_human |
Flag details:
--schema <file>— the schema to check against, equivalent to the positionalvalidateargument. Required for schema checking; without it only JSON syntax is checked.--no-verify—repaironly. Skips theJSON.parseround trip that proves the repaired text is valid, and writes the raw repaired text anyway.valid_after_repairbecomesnull.--repair-out <file>—jsonlonly. Writes the usable JSONL stream described above.--limit <n>—jsonlonly. Maximum non-empty lines to read, 1..100000. Reaching it setstruncated: true,truncated_reason: "max_lines".--max-errors <n>—jsonlonly. Maximum problems to report, 0..100000. Reaching it setstruncated_reason: "max_errors".--max-bytes <n>— all three. 1..52428800. Exceeding it is a hard failure (exit 1), never a silent truncation.--max-repair-depth <n>—repaironly. 1..100000, default 1000. A repaired document nesting deeper than this is refused and nothing is written.
Limits
| Limit | Default | Flag | Behaviour when exceeded |
MAX_BYTES | 50 MB (52 428 800 bytes) | --max-bytes | Hard failure, exit 1, nothing written |
MAX_LINES | 100 000 non-empty lines | --limit (jsonl) | Reading stops: truncated: true, truncated_reason: "max_lines" |
MAX_ERRORS | 100 reported problems | --max-errors (jsonl) | Reading stops: truncated: true, truncated_reason: "max_errors" |
MAX_REPAIR_DEPTH | 1000 nesting levels | --max-repair-depth (repair) | Hard failure, exit 1, nothing written |
| Line preview | 200 characters | none | data is cut, data_truncated: true |
Examples
Check a model response against a schema and read the failure as a sentence:
node scripts/index.js validate response.json schema.json"valid": false with errors[0].summary of /age must be integer tells you the exact JSON pointer to fix, and error_count tells you whether it is one problem or thirty.
Just prove it parses:
node scripts/index.js validate - < response.json"schema_checked": false and summary: "the input is valid JSON (no schema was checked)" — syntax only, and the payload says so instead of pretending a schema ran.
Repair a broken file and keep the proof:
node scripts/index.js repair broken.json fixed.jsonchanged: true, valid_after_repair: true, repairs naming unquoted_key/single_quoted_string/trailing_comma, and before.sha256 !== after.sha256 together prove the file was rewritten and that the result parses.
Repair straight from a pipe:
curl -s "$URL" | node scripts/index.js repair -The repaired document comes back as data.value in the single stdout line, so you can capture it with jq .data.value.
Find the broken line in a big log without loading it:
node scripts/index.js jsonl events.jsonl --schema event.schema.jsonerrors[0].line is the real file line, errors[0].data shows the offending text, and lines/parsed/valid tell you whether the damage is one line or the whole tail.
Rebuild a usable JSONL from the salvageable parts:
node scripts/index.js jsonl events.jsonl --repair-out events.fixed.jsonlrepaired_lines and unrepairable add up to the number of broken lines, so you know exactly how much was salvaged and how much was dropped.
Notes and limitations
- Repair can silently change data.
{"a": "unterminatedbecomes{"a":"unterminated"}: the missing quote and brace are invented. A successful repair is not evidence that the data is correct, only that it parses. Always runvalidateagainst a schema afterwards. - Repair is conservative in one specific way: if the input already parses, it is left alone (
changed: false,written: false, no file created). changedcompares the trimmed input to the trimmed repaired text, so pure whitespace/formatting differences count aschanged: false.repairsis a heuristic scan of the input text for known defect classes (unquoted_key,single_quoted_string,trailing_comma,comment,python_literal,unquoted_value,missing_value,ellipsis,unclosed_bracket). It is a hint about what was wrong, not a log of what jsonrepair did, and it never contains your values.before/aftersha256 hashes are of the exact texts, so you can verify the change independently of this tool.- Ajv is configured with
strict: false,allErrors: trueandallowUnionTypes: true: unknown keywords are ignored rather than fatal, every violation is reported, and["string","null"]is accepted. formatis not asserted (ajv-formatsis not installed). Usepattern,minLengthor a real check if you need the constraint enforced.- Relative
$refresolves against the schema file's own directory: every*.jsonfile sitting next to the schema is registered under its bare name and itsfile://URL. A$refto a file one directory up, or tohttp(s)://, fails to compile and exits 1. - Draft-07 is the supported dialect. A 2020-12
$schemaline is accepted but its vocabulary keywords are not implemented. validatereturns exit code 0 even when the document is invalid: invalid data is a result, not a crash. Exit 1 means the tool could not do its job (missing file, uncompilable schema, unreadable input).jsonlcounts a line as valid only if it parses and passes the schema when one is given. Without--schema,validjust means "parsed".- Blank lines in a JSONL file are skipped and do not shift the reported line numbers, which always refer to the physical file.
jsonlstreams; it never holds more than one line plus the error list in memory.validateandrepairdo hold the whole document, bounded byMAX_BYTES.
Troubleshooting
the input is not valid JSON: Expected property name or '}' ...with exit code 0 — the file is broken; runrepairand thenvalidateagain.jsonrepair could not repair the input (at position 9): ...— jsonrepair gave up. Nothing was written. The position is a character offset in the input.the repaired document nests N levels deep, over the N level limit— jsonrepair closed thousands of brackets for you. Raise--max-repair-depthonly if you trust the input.the repaired text still does not parse, nothing was written: ...— jsonrepair produced text thatJSON.parserejects. Use--no-verifyto get the raw text for inspection, or fix the input by hand.schema X could not be compiled: can't resolve reference ./y.json— the referenced schema is not a*.jsonfile in the same directory, or it is not valid JSON. Move it next to the schema or inline it.input not found: X/input is not a regular file: X— bad path.-is the only way to read stdin.unexpected argument: ...— you passed a third positional; runnode scripts/index.js <command> --help.truncated: trueinjsonl— you hit--limitor--max-errors; the counts inlines/valid/invaliddescribe only what was examined.
In this repository12 other files it gives its agents.
- ast-aware-js-repo-auditSkill
- env-doctorSkill
- git-change-intelligenceSkill
- image-transform-pipelineSkill
- launch-auditSkill
- ocr-to-markdownSkill
- osint-investigative-agentSkill
- pdfSkill
- secret-and-config-auditSkill
- sqlite-agent-toolkitSkill
- streaming-csv-json-normalizerSkill
- workspace-archivesSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

