Repair malformed JSON from LLMs, APIs, logs, and user input in Python.
Repair malformed JSON from LLMs, APIs, logs, and user input in Python.
English | 中文
Repair malformed JSON from LLMs, APIs, logs, and user input in Python.
json.loads() or as a schema-guided repair step.pip install json-repair or try the live demo.import json_repair
bad_json = '{"users":[{"name":"Ada","role":"admin",}],"ok":true'
decoded_object = json_repair.loads(bad_json)
# {'users': [{'name': 'Ada', 'role': 'admin'}], 'ok': True}
If json_repair saves you time, star the repository so more people can find it.
If you are unsure whether this library will fix your specific problem, or simply want your JSON validated online, try one of these:
This library is free for everyone and is maintained as a side project, so if it helps your work, consider becoming a sponsor: https://github.com/sponsors/mangiucugna
Some LLMs are a bit iffy when it comes to returning well formed JSON data, sometimes they skip a parentheses and sometimes they add some words in it, because that's what an LLM does. Luckily, the mistakes LLMs make are simple enough to be fixed without destroying the content.
I searched for a lightweight python package that was able to reliably fix this problem but couldn't find any.
So I wrote one
true/false/null and None are recognized case-insensitively as JSON booleans or null.Install the library with pip
pip install json-repair
then you can use use it in your code like this
from json_repair import repair_json
good_json_string = repair_json(bad_json_string)
# If the string was super broken this will return an empty string
You can use this library to completely replace json.loads():
import json_repair
decoded_object = json_repair.loads(json_string)
or just
import json_repair
decoded_object = json_repair.repair_json(json_string, return_objects=True)
Some users of this library adopt the following pattern:
obj = {}
try:
obj = json.loads(string)
except json.JSONDecodeError as e:
obj = json_repair.loads(string)
...
This is wasteful because json_repair already does that strict json.loads() check for you by default. The normal flow is:
json.loads() / json.load() firstUse the default call unless you explicitly want to skip that initial validation step:
import json_repair
decoded_object = json_repair.loads(json_string)
JSON repair provides also a drop-in replacement for json.load():
import json_repair
try:
file_descriptor = open(fname, 'rb')
except OSError:
...
with file_descriptor:
decoded_object = json_repair.load(file_descriptor)
and another method to read from a file:
import json_repair
try:
decoded_object = json_repair.from_file(json_file)
except OSError:
...
except IOError:
...
Keep in mind that the library will not catch any IO-related exception and those will need to be managed by you
When working with non-Latin characters (such as Chinese, Japanese, or Korean), you need to pass ensure_ascii=False to repair_json() in order to preserve the non-Latin characters in the output.
Here's an example using Chinese characters:
repair_json("{'test_chinese_ascii':'统一码'}")
will return
{"test_chinese_ascii": "\u7edf\u4e00\u7801"}
Instead passing ensure_ascii=False:
repair_json("{'test_chinese_ascii':'统一码'}", ensure_ascii=False)
will return
{"test_chinese_ascii": "统一码"}
More in general, repair_json will accept all parameters that json.dumps accepts and just pass them through (for example indent)
By default, json_repair first tries the standard-library JSON loader and only falls back to the repair parser when strict JSON parsing fails.
If you already know the input is invalid JSON and want to skip that initial validation step, pass skip_json_loads=True:
from json_repair import repair_json
good_json_string = repair_json(bad_json_string, skip_json_loads=True)
This is an explicit tradeoff:
skip_json_loads=True: skip the validation fast path and go straight to the repair parserImportant: skip_json_loads=True is only for inputs you already know are invalid. If you force already-valid JSON through the repair parser, json_repair may still "repair" it and can change the resulting structure or values. If you need valid JSON to be preserved as-is, keep skip_json_loads=False.
json_repair intentionally keeps the validation path on the standard library. It does not auto-detect or auto-use third-party JSON libraries, which keeps behavior predictable and avoids extra overhead on the common path.
Some rules of thumb to use:
return_objects=True will always be faster because the parser returns an object already and it doesn't have serialize that object to JSONskip_json_loads=True is faster only if you 100% know that the string is not a valid JSONskip_json_loads=True is not a "faster but equivalent" mode for valid JSON; it intentionally bypasses the stdlib success path, so valid inputs should use the default behaviorr"string with escaping\""If you want non-stdlib JSON semantics or a different performance profile, use your preferred JSON library yourself instead of expecting json_repair to switch parsers automatically. orjson is a common example people ask about, and the same pattern applies to any other JSON library.
Recommended patterns:
Strict JSON first, repair only if needed:
import json_repair
decoded_object = json_repair.loads(json_string)
Known-bad input, so skip the validation step:
from json_repair import repair_json
decoded_object = repair_json(bad_json_string, return_objects=True, skip_json_loads=True)
orjson first, json_repair only as a fallback:
import json_repair
import orjson
try:
decoded_object = orjson.loads(json_string)
except orjson.JSONDecodeError:
decoded_object = json_repair.loads(json_string, skip_json_loads=True)
By default json_repair does its best to “fix” input, even when the JSON is far from valid.
In some scenarios you want the opposite behavior and need the parser to error out instead of repairing; pass strict=True to repair_json, loads, load, or from_file to enable that mode:
from json_repair import repair_json
repair_json(bad_json_string, strict=True)
The CLI exposes the same behavior with json_repair --strict input.json (or piping data via stdin).
In strict mode the parser raises ValueError as soon as it encounters structural issues such as duplicate keys, missing : separators, empty keys/values introduced by stray commas, multiple top-level elements, or other ambiguous constructs. This is useful when you just need validation with friendlier error messages while still benefiting from json_repair’s resilience elsewhere in your stack.
Strict mode still honors skip_json_loads=True; combining them lets you skip the initial json.loads check but still enforce strict parsing rules.
Schema-guided repairs are currently considered in beta. Bugs are to be expected.
You can guide repairs with a JSON Schema (or a Pydantic v2 model). When enabled, the parser will:
"1" → 1 for integer fields, and "yes"/"no"/1/0 for booleans).Schema mode can be selected with schema_repair_mode:
standard (default): existing schema-guided behavior.salvage: includes standard and also:[{...}] -> {...});default, const, first enum, or empty array/object when allowed by schema constraints).This is especially useful when you need deterministic, schema-valid outputs for downstream validation, storage, or typed processing. If the input cannot be repaired to satisfy the schema, json_repair raises ValueError.
Install the optional dependencies:
pip install 'json-repair[schema]'
(For CLI usage, you can also use pipx install 'json-repair[schema]'.)
When schema is provided, schema guidance is always applied (for both valid and invalid JSON). Schema guidance is mutually exclusive with strict=True.
…
Pydantic v2 model example:
from pydantic import BaseModel, Field
from json_repair import repair_json
class Payload(BaseModel):
value: int
tags: list[str] = Field(default_factory=list)
repair_json(
'{"value": "1", "tags": }',
schema=Payload,
skip_json_loads=True,
return_objects=True,
)
Sometimes you are streaming some data and want to repair the JSON coming from it. Normally this won't work but you can pass stream_stable to repair_json() or loads() to make it work:
stream_output = repair_json(stream_input, stream_stable=True)
If you want copy-paste examples for real applications, see examples/README.md:
No open issues yet, or sync has not completed.