JSON Essentials¶
The data format of the web and of almost every AI API: requests, responses, configs, and datasets all speak JSON. Simple to read, trivial in Python and JavaScript — but the details (types, escaping, big numbers) bite.
Table of Contents¶
- Installation / Setup
- Core Syntax
- Common Operations
- Practical Examples
- Common Patterns
- Best Practices
- Common Mistakes
- Troubleshooting
- Related Cheat Sheets
- Official Documentation
Installation / Setup¶
Built into every language — nothing to install:
Core Syntax¶
The JSON data types¶
| JSON | Example | Python | JavaScript |
|---|---|---|---|
| object | {"a": 1} |
dict |
object {} |
| array | [1, 2] |
list |
array [] |
| string | "hi" |
str |
string |
| number | 3.14, -2, 1e5 |
int/float |
number |
| boolean | true / false |
True/False |
true/false |
| null | null |
None |
null |
Hard syntax rules (why parsers reject things)¶
- Keys must be double-quoted strings:
{"a": 1}✓,{a: 1}✗,{'a': 1}✗. - Strings must use double quotes.
- No trailing commas, no comments, no
undefined/NaN. - Numbers: no leading zeros; integers beyond ±2⁵³ lose precision in JS.
{
"model": "v2",
"classes": ["pos", "neg"],
"threshold": 0.5,
"probabilities": [0.93, 0.07],
"metadata": { "runtime_ms": 12, "cached": true, "error": null }
}
Common Operations¶
Python: serialize (encode) & deserialize (decode)¶
import json
# dict/list -> JSON string
s = json.dumps({"a": 1, "b": [1, 2]}) # '{"a": 1, "b": [1, 2]}'
s = json.dumps(obj, indent=2) # pretty, for files
s = json.dumps(obj, sort_keys=True) # deterministic output
# JSON string -> Python object
data = json.loads('{"a": 1}') # {'a': 1}
# files
with open("config.json") as f:
cfg = json.load(f) # file -> object
with open("out.json", "w") as f:
json.dump(data, f, indent=2) # object -> file
JavaScript: parse & stringify¶
const obj = JSON.parse('{"label":"pos","score":0.93}');
const text = JSON.stringify({ label: "pos", score: 0.93 });
const pretty = JSON.stringify(obj, null, 2);
Handling non-JSON types (the Python gotchas)¶
import json, numpy as np
# 1. Tuples -> arrays: default str()? No — use default:
def default(o):
if isinstance(o, (np.ndarray, np.generic)):
return o.tolist()
if isinstance(o, set):
return sorted(o)
raise TypeError(f"not serializable: {type(o)}")
json.dumps({"v": np.float32(0.5)}, default=default) # '{"v": 0.5}'
# 2. Dates / other objects -> isoformat string
json.dumps({"ts": datetime.now().isoformat()})
# 3. NaN / Infinity: strict JSON forbids them — sanitize first
# json.dumps(float("nan")) -> "NaN" (not strict JSON!)
# use allow_nan=False to catch it
// Dates and special values are not JSON types either:
JSON.stringify({ d: new Date() }); // date becomes ISO string automatically
Practical Examples¶
1. The API round trip (Python ↔ wire)¶
import requests, json
payload = {"text": "great product", "model": "v2"}
r = requests.post(url, json=payload) # dict -> JSON body
data = r.json() # JSON body -> dict
# always round-trip cleanly:
assert json.loads(json.dumps(payload)) == payload
2. Parse nested LLM API responses¶
resp = {
"choices": [{"message": {"content": "Sure!",
"tool_calls": [{"function": {"name": "search",
"arguments": "{\"q\": \"rag\"}"}}]}}]
}
content = resp["choices"][0]["message"]["content"]
args = json.loads(resp["choices"][0]["message"]["tool_calls"][0]["function"]["arguments"])
# tool arguments arrive as a JSON *string* — parse them!
3. Config + results files in a project¶
# save hyperparameters and metrics together
run = {"config": cfg, "metrics": {"accuracy": 0.91, "f1": 0.88},
"model_path": "artifacts/v2.pt", "ts": datetime.now().isoformat()}
with open("runs/run_42.json", "w") as f:
json.dump(run, f, indent=2)
Common Patterns¶
# Read many JSON lines (JSONL) — one object per line
# 1 object per line: standard for datasets & logs
with open("data.jsonl") as f:
rows = [json.loads(line) for line in f]
# Write JSONL efficiently
with open("out.jsonl", "w") as f:
for row in rows:
f.write(json.dumps(row) + "\n")
# Schema validation (typed, readable errors)
from pydantic import BaseModel
class Prediction(BaseModel):
label: str
score: float = 0.0
Prediction(**data) # raises a clear error if fields/types wrong
# inspect an API response at the CLI
curl -s https://api.example.com/v1/health | jq '.status, .version'
Best Practices¶
- Round-trip discipline: anything you
json.dumpsshouldjson.loadsback identically — test it. - Keep API payloads flat-ish and typed; use pydantic/TypedDict on the Python side.
- Use
indent=2for human files, no indent for network/streaming. - Sanitize floats: JSON has no NaN/Infinity — filter them (
allow_nan=Falsein Python to catch). - Big integers and floats: use
strfor ids beyond 2⁵³ in JS-facing JSON; prefer decimal strings for money. - Never
evalJSON — parse it (eval is code execution). - Pretty-print in logs only, keep stored JSON canonical (sorted keys) for diffing.
- Model outputs: use JSON Schema (OpenAPI) to constrain LLM outputs and validate with pydantic.
Common Mistakes¶
| Mistake | Consequence | Fix |
|---|---|---|
| Single quotes / trailing commas | ParseError | Strict double-quoted, no commas |
NaN/Infinity in output |
Invalid JSON for other tools | allow_nan=False; sanitize |
| NumPy floats/arrays directly | TypeError: Object of type float32 is not JSON serializable |
default= → tolist() |
Dates serialized as "datetime(...)" |
Bad strings | .isoformat() |
| Big ids as numbers | Silent precision loss in JS | Stringify ids |
eval / JSON.parse on unvalidated data |
Code execution / crashes | json.loads (Python), parse errors handled |
Forgetting ensure_ascii=False |
\uXXXX escapes in files (readable loss) |
json.dumps(obj, ensure_ascii=False) |
| LLM returns JSON in markdown fences | Parsing fails | Strip `` fences beforejson.loads` |
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
JSONDecodeError: Expecting property name enclosed in double quotes |
Single-quoted keys | Produce valid JSON server-side |
Expecting value: line 1 column 1 |
Empty/HTML response, not JSON | Print resp.text first |
Object of type X is not JSON serializable |
Non-JSON Python object | default hook or serialize to str first |
| Tool call arguments unparseable | Nested string escaped twice | json.loads after unescaping (\n, \") |
| Numbers change value in JS | > 2⁵³ | Send as strings |
json.load on JSONL |
Fails on line 2 | It's one-object-per-line — iterate lines |
| CORS/encoding mojibake | Wrong charset | ensure_ascii=False + utf-8 file I/O |
Related Cheat Sheets¶
- http.md — JSON is the API body format
- javascript.md —
JSON.parse/stringifyin the browser - APIs — schemas and validation
- python-typing.md — TypedDict for JSON structures
- Other Tools — YAML, the config cousin of JSON
- Home · Central Navigation Index
Official Documentation¶
Last Updated¶
2026-09-06 — Initial version.