3 min read
Safe parsing with json.loads vs ast.literal_eval. This lab reports ops/s on Linux localhost for an object, a 100-int list, and a nested structure — both arms avoid eval.
Related links:
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. JSON text from json.dumps; Python text from repr() for literal_eval. Objects equal (equal=True).
Verdict up front (n=20000): object — json ~531562 ops/s vs literal_eval ~36193; nested — json ~192005 vs literal_eval ~6216.
| Arm | Pattern |
|---|---|
| json.loads object | compact JSON |
| literal_eval object | Python repr |
| list of 100 ints | both parsers |
| nested dict/list | both parsers |
Seven rounds, p50. Wire sizes: JSON 119 B, repr 138 B.
Script: lab-evidence/137-literal-eval-vs-json/results/run_lab.py.
| Arm | ops/s |
|---|---|
| json.loads object | 531562 |
| literal_eval object | 36193 |
| json.loads list100 | 204844 |
| literal_eval list100 | 11490 |
| json.loads nested | 192005 |
| literal_eval nested | 6216 |
JSON led every column — roughly 14.7× on the object arm.
ast.literal_eval, never eval.eval; neither replaces a schema validator.Admin pastes, debug __repr__ dumps, and some legacy .cfg fragments ship Python literals. literal_eval is the correct safe parser there — just budget ~36193 ops/s vs JSON’s ~531562 on similar data.
Nested structures widen the gap (json ~192005 vs literal_eval ~6216). Prefer JSON for anything on a hot ingest path.
JSON is the right default for anything that crosses a process or language boundary: ~531562 ops/s here beats literal_eval’s ~36193, and every other service already speaks it. Keep ast.literal_eval in the toolbox for trusted Python-literal pastes and __repr__ forensics — especially lists of ints where the gap grew to json ~204844 vs literal_eval ~11490.
Put a hard byte cap in front of either parser when input can be attacker-controlled; safe parsing is not the same as cheap parsing of huge trees.
If a runbook still says “just eval the blob,” replace that line with literal_eval or JSON and re-test — correctness first, then the ops/s table above.
eval because literal_eval “felt slow.”true/null) to literal_eval.Evidence: summary.json, summary.txt.
One Linux box. CPython parsers only. Not orjson/ujson, not YAML.
Document which parser each config path uses so on-call does not “fix” a slow ingest by swapping JSON for repr dumps under pressure.
Measure your own payload shapes before rewriting parsers in a hot path.
json.loads ~531562 ops/s vs ast.literal_eval ~36193 on a small object. Prefer JSON for interchange; keep literal_eval for trusted Python-literal text only.
Lab evidence
Ran the literal_eval-vs-json lab on Linux localhost today with Python 3.13.5, seven rounds and p50 ops/s. On the object arm, json.loads measured ~531562 ops/s versus ast.literal_eval ~36193; nested text measured ~192005 versus ~6216. JSON led every column; both arms avoided eval. Affiliates: 0.