Repair and parse Python/JSON-like literals containing raw control characters embedded inside quoted strings
Project description
litfix
Repair and parse Python/JSON-like literal strings that contain raw, unescaped control characters embedded inside quoted string values.
The problem
>>> import ast
>>> data = "{'title': 'Azur\r\nLane', 'new': True}"
>>> ast.literal_eval(data)
SyntaxError: unterminated string literal (detected at line 1)
This happens when upstream code builds a dict/list containing strings
that already have real \r/\n bytes in them (e.g. scraped HTML,
multi-line titles), then serializes it with str(obj) instead of
repr(obj) or json.dumps(obj). The result looks like a normal
Python-repr or JSON string, but has literal control characters sitting
inside the quotes -- which is invalid syntax for ast.literal_eval,
json.loads, and lenient parsers like json5 alike, since none of them
allow a bare newline inside a quoted string.
The fix
litfix walks the source character by character, tracks whether the
cursor is inside a quoted string (honoring backslash escapes), and
re-escapes control characters only when they're inside quotes.
Whitespace between tokens (e.g. pretty-printed line wrapping) is left
untouched, since it's already valid syntax there.
>>> from litfix import parse
>>> parse("{'title': 'Azur\r\nLane', 'new': True}")
{'title': 'Azur\r\nLane', 'new': True}
Install
pip install litfix # once published
pip install -e ".[dev]" # local editable install with test deps
Zero runtime dependencies.
API
from litfix import (
parse, # auto-detect: Python literal, then JSON
robust_literal_eval, # ast.literal_eval with auto-repair
robust_json_loads, # json.loads with auto-repair
sanitize_literal, # just the repair step, for custom pipelines
RepairReport, # diagnostics: what got fixed, and where
LiteralRepairError, # raised if repair still can't make it parse
)
parse(s, *, verbose=False)
Best-effort parse: tries Python-literal syntax first (single quotes,
True/False/None), falls back to JSON. This is the one you want if
you don't know or don't care which flavor the source uses.
parse("[{'a': 1, 'b': True}]") # -> [{'a': 1, 'b': True}]
parse('{"a": 1, "b": true}') # -> {'a': 1, 'b': True}
robust_literal_eval(s, *, verbose=False) / robust_json_loads(s, *, verbose=False)
Same repair strategy, pinned to one syntax. Pass verbose=True to get
back (result, RepairReport) instead of just result:
result, report = robust_literal_eval(raw, verbose=True)
if report.was_modified:
print(report) # "repaired 2 control char(s) inside string literals ('\r'x1, '\n'x1)"
sanitize_literal(s, *, report=None)
The raw repair pass, if you want to wire it into your own pipeline
before doing something other than literal_eval/json.loads with it.
Errors
If the input still can't be parsed after sanitization, litfix raises
LiteralRepairError (a ValueError) with .original_error (the
underlying SyntaxError/JSONDecodeError) and .source (the sanitized
text that was attempted), so you can see exactly what was tried.
CLI
litfix dump.txt # parse -> pretty JSON on stdout
litfix dump.txt -o clean.json # write to a file instead
cat dump.txt | litfix -v # read stdin, print repair diagnostics to stderr
litfix dump.txt --mode literal # force Python-literal parsing
litfix dump.txt --indent 0 # compact single-line JSON output
Exit code is 0 on success, 1 on unrecoverable parse failure (with an
error message on stderr).
Testing
pip install -e ".[dev]"
pytest
License
MIT
👤 Author
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file litfix-1.0.0.tar.gz.
File metadata
- Download URL: litfix-1.0.0.tar.gz
- Upload date:
- Size: 10.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2c14788209b1a9efe5f2df6c9d9dfff315410a88bea5fbb4cab6fa72090161b
|
|
| MD5 |
87a81c92807eab34b3f27462d87ca3d2
|
|
| BLAKE2b-256 |
917bc8b14282b5aed9b128ffb3aef077c95e234c51a398970b09361ff3c258bb
|
File details
Details for the file litfix-1.0.0-py3-none-any.whl.
File metadata
- Download URL: litfix-1.0.0-py3-none-any.whl
- Upload date:
- Size: 9.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95c2b27a9bf7129dcc116de2601501c9930209cad54be7d80669dcee0f1c8d12
|
|
| MD5 |
105ed7b27c8d8350970360a6f3d95973
|
|
| BLAKE2b-256 |
39c5a9cd5e1a1ac69608dc8a9f21f79218746298533bb20c4a65bbb1573e71a4
|