Skip to content

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

Built into every language — nothing to install:

import json                     # Python
JSON.parse / JSON.stringify     // JavaScript (global)
jq . file.json                  # CLI (see linux-commands.md)

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.dumps should json.loads back identically — test it.
  • Keep API payloads flat-ish and typed; use pydantic/TypedDict on the Python side.
  • Use indent=2 for human files, no indent for network/streaming.
  • Sanitize floats: JSON has no NaN/Infinity — filter them (allow_nan=False in Python to catch).
  • Big integers and floats: use str for ids beyond 2⁵³ in JS-facing JSON; prefer decimal strings for money.
  • Never eval JSON — 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

Official Documentation

Last Updated

2026-09-06 — Initial version.