Skip to content

[Bug]: Repair time grows quadratically with escapes in a string #245

Closed

Description

@den-run-ai

Version of the library

0.64.3

Describe the bug

Every escape in a string on the repair path (\n, \", \uXXXX, …) calls _rebuild_unmatched_opening_braces, which rescans the whole string accumulated so far. Time grows with escapes × length, the same kind of slowdown as #203 but for escapes. A truncated tool call carrying a 1,000-line file takes 3.8 s on CPython 3.13.

How to reproduce

from json_repair import repair_json

line = '    value = compute(item, \\"name\\")\\n'
repair_json('{"a": "' + line * 1000)

Expected behavior

Time roughly linear in the input: about 50 ms here. I have a small fix and will open a PR.

Activity

  1. den-run-ai commented on Nov 3, 2026

    @den-run-ai
    Author

    Opening PRs here is limited to collaborators, so the fix is on my fork (branch fix-escape-rescan, 2 files, +21/−26). Happy to open a PR if you allow it, or feel free to cherry-pick.

    Each escape rescanned the whole accumulated string to rebuild the brace/bracket state. Dropping the trailing backslash never changes that state, so only the decoded replacement needs scanning. Output is unchanged: the test suite passes, and a 200,000-input differential fuzz against main (four option sets, including logging) matched exactly. The new test in test_performance.py fails on main.

    Median time per repair, before → after:

    Input CPython 3.13 PyPy 3.11, warm Pystachy, AOT
    Truncated string, 1,000 lines of code (37 KB) 3.74 s → 48 ms 517 → 69 ms 1.05 s → 3.7 ms
    String with 4,000 \n escapes (12 KB) 1.18 s → 12 ms 146 → 8.7 ms 330 → 2.2 ms
    Short string with 4 escapes 33 → 31 µs 7.2 → 7.3 µs 6.9 → 6.2 µs

    Five interleaved processes per cell (seven for the short string), each discarding eight warm-up batches; one pinned core, Linux x86-64 (Xeon 2.3 GHz), PyPy 8.0.1.

    We found this while porting json_repair's core (the in-memory parser) to Pystachy, a compiler for typed Python. The Pystachy column runs that port compiled ahead of time, with this rescan restored for "before". With the fix in both, the compiled port is up to 13× faster than CPython on these inputs (5× on the short string).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions