Skip to content
LangStop

Tutorials

Keyboard Shortcuts

ActionShortcut
Toggle SidebarCtrl+B
Save TabCtrl+S
Close TabAlt+W
Switch to Tab 1Alt+Shift+1
Switch to Tab 2Alt+Shift+2
Switch to Tab 3Alt+Shift+3
Switch to Tab 4Alt+Shift+4
Switch to Tab 5Alt+Shift+5
Switch to Tab 6Alt+Shift+6
Switch to Tab 7Alt+Shift+7
Switch to Tab 8Alt+Shift+8
Switch to Tab 9Alt+Shift+9

How to Parse JSON in Python — json.loads() Guide

What is JSON Parsing in Python?

JSON parsing in Python is the process of converting a JSON-formatted string into native Python data structures. Python's standard library includes the json module, which provides all the tools needed to parse, manipulate, and serialize JSON data without installing any third-party packages.

The json module maps JSON types directly to Python types, making it seamless to work with API responses, configuration files, and data storage formats.


Python json Module Overview

The json module is part of Python's standard library — no pip install required:

import json

Core Functions

Function Purpose
json.loads() Parse a JSON string into a Python object
json.load() Parse a JSON file into a Python object
json.dumps() Convert a Python object to a JSON string
json.dump() Write a Python object to a file as JSON

json.loads() vs json.load()

json.loads() — Parse from a String

Use json.loads() when you have JSON data as a string:

import json
 
json_string = '{"name": "Alice", "age": 30, "is_developer": true}'
data = json.loads(json_string)
 
print(data)           # {'name': 'Alice', 'age': 30, 'is_developer': True}
print(type(data))     # <class 'dict'>
print(data["name"])   # Alice
``'
 
### json.load() — Parse from a File
 
Use `json.load()` when reading JSON directly from a file:
 
```python
import json
 
with open("data.json", "r", encoding="utf-8") as file:
    data = json.load(file)
 
print(data["name"])

The key difference is the input source: json.loads() takes a string, while json.load() takes a file object. Both return the same Python data structures.


JSON to Python Type Mapping

When json.loads() converts JSON to Python, types map as follows:

JSON Type Python Type Example
object dict {"a":1}{'a': 1}
array list [1,2][1, 2]
string str "hello"'hello'
number (int) int 4242
number (float) float 3.143.14
true True trueTrue
false False falseFalse
null None nullNone
import json
 
data = json.loads('''
{
  "name": "Alice",
  "age": 30,
  "height": 1.75,
  "is_developer": true,
  "spouse": null,
  "hobbies": ["reading", "hiking"],
  "address": {"city": "New York", "zip": "10001"}
}
''')
 
print(type(data["name"]))          # <class 'str'>
print(type(data["age"]))           # <class 'int'>
print(type(data["height"]))        # <class 'float'>
print(type(data["is_developer"]))  # <class 'bool'>
print(type(data["spouse"]))        # <class 'NoneType'>
print(type(data["hobbies"]))       # <class 'list'>
print(type(data["address"]))       # <class 'dict'>

Parsing JSON Arrays

JSON arrays become Python lists:

import json
 
json_array = '["apple", "banana", "cherry"]'
fruits = json.loads(json_array)
 
print(fruits)       # ['apple', 'banana', 'cherry']
print(fruits[1])    # banana
print(len(fruits))  # 3

Arrays of objects are common in API responses:

import json
 
users_json = '''
[
  {"id": 1, "name": "Alice", "role": "Engineer"},
  {"id": 2, "name": "Bob", "role": "Designer"},
  {"id": 3, "name": "Charlie", "role": "Manager"}
]
'''
 
users = json.loads(users_json)
 
for user in users:
    print(f"{user['id']}: {user['name']} ({user['role']})")
# Output:
# 1: Alice (Engineer)
# 2: Bob (Designer)
# 3: Charlie (Manager)

You can also use list comprehensions and other Python idioms:

engineers = [u for u in users if u["role"] == "Engineer"]
print(engineers)  # [{'id': 1, 'name': 'Alice', 'role': 'Engineer'}]

Parsing Nested JSON

Deeply nested JSON structures are fully preserved:

import json
 
nested_json = '''
{
  "company": {
    "name": "TechCorp",
    "founded": 2020,
    "location": {
      "address": "123 Main St",
      "city": "San Francisco",
      "coordinates": {
        "lat": 37.7749,
        "lng": -122.4194
      }
    },
    "departments": [
      {
        "name": "Engineering",
        "headcount": 50,
        "teams": ["Frontend", "Backend", "DevOps"]
      },
      {
        "name": "Design",
        "headcount": 15,
        "teams": ["UI", "UX", "Brand"]
      }
    ]
  }
}
'''
 
data = json.loads(nested_json)
 
print(data["company"]["name"])                          # TechCorp
print(data["company"]["location"]["city"])              # San Francisco
print(data["company"]["location"]["coordinates"]["lat"]) # 37.7749
print(data["company"]["departments"][0]["teams"][1])    # Backend

object_hook — Custom Object Decoding

The json.loads() function accepts an object_hook parameter that lets you transform decoded dictionaries into custom Python objects:

import json
from dataclasses import dataclass
 
@dataclass
class Person:
    name: str
    age: int
    is_developer: bool
 
def person_decoder(dct):
    if "name" in dct and "age" in dct:
        return Person(**dct)
    return dct
 
json_data = '{"name": "Alice", "age": 30, "is_developer": true}'
person = json.loads(json_data, object_hook=person_decoder)
 
print(person)              # Person(name='Alice', age=30, is_developer=True)
print(type(person))        # <class '__main__.Person'>
print(person.name)         # Alice
print(person.age)          # 30

Use Case: Parsing Nested Custom Types

The object_hook is called recursively for each JSON object in the tree:

import json
from datetime import datetime
 
def custom_decoder(dct):
    if "__type__" in dct:
        type_name = dct["__type__"]
        if type_name == "datetime":
            return datetime.fromisoformat(dct["value"])
        elif type_name == "complex":
            return complex(dct["real"], dct["imag"])
    return dct
 
json_data = '''
{
  "event": "Conference",
  "start": {"__type__": "datetime", "value": "2026-09-20T09:00:00"},
  "end": {"__type__": "datetime", "value": "2026-09-22T18:00:00"},
  "coefficient": {"__type__": "complex", "real": 3, "imag": 4}
}
'''
 
data = json.loads(json_data, object_hook=custom_decoder)
print(data["start"])       # 2026-09-20 09:00:00
print(type(data["start"])) # <class 'datetime.datetime'>
print(data["coefficient"]) # (3+4j)
print(type(data["coefficient"])) # <class 'complex'>

Datetime Handling

JSON has no native date or datetime type. Common strategies for handling dates:

Strategy 1: Manual Parsing After Load

import json
from datetime import datetime
 
json_data = '{"name": "Event", "date": "2026-06-21", "timestamp": "2026-06-21T14:30:00"}'
data = json.loads(json_data)
 
# Parse manually
data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
data["timestamp"] = datetime.fromisoformat(data["timestamp"])
 
print(data["date"])        # 2026-06-21
print(type(data["date"]))  # <class 'datetime.date'>

Strategy 2: Using object_hook

import json
from datetime import datetime
 
def datetime_hook(dct):
    for key, value in dct.items():
        if isinstance(value, str):
            try:
                dct[key] = datetime.fromisoformat(value)
            except (ValueError, TypeError):
                pass
    return dct
 
json_data = '{"created_at": "2026-06-21T14:30:00", "updated_at": "2026-06-21T15:00:00", "name": "test"}'
data = json.loads(json_data, object_hook=datetime_hook)
 
print(data["created_at"])  # 2026-06-21 14:30:00
print(type(data["created_at"]))  # <class 'datetime.datetime'>

Strategy 3: Custom JSON Encoder/Decoder (Round-trip)

For full round-trip support, create a custom encoder and decoder:

import json
from datetime import datetime, date
 
class DateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (datetime, date)):
            return {"__type__": "datetime", "value": obj.isoformat()}
        return super().default(obj)
 
def datetime_decoder(dct):
    if dct.get("__type__") == "datetime":
        return datetime.fromisoformat(dct["value"])
    return dct
 
# Serialize
data = {"event": "Conference", "date": datetime(2026, 6, 21, 14, 30)}
json_str = json.dumps(data, cls=DateTimeEncoder)
print(json_str)
# {"event": "Conference", "date": {"__type__": "datetime", "value": "2026-06-21T14:30:00"}}
 
# Deserialize
restored = json.loads(json_str, object_hook=datetime_decoder)
print(restored["date"])  # 2026-06-21 14:30:00
print(type(restored["date"]))  # <class 'datetime.datetime'>

json.dumps() — Serialization

Convert Python objects back to JSON strings:

import json
 
data = {
    "name": "Alice",
    "age": 30,
    "skills": ["Python", "Django", "PostgreSQL"],
    "is_active": True,
    "metadata": None,
}
 
json_string = json.dumps(data)
print(json_string)
# {"name": "Alice", "age": 30, "skills": ["Python", "Django", "PostgreSQL"], "is_active": true, "metadata": null}

Pretty Printing with indent

The indent parameter produces human-readable output:

pretty_json = json.dumps(data, indent=2)
print(pretty_json)
# {
#   "name": "Alice",
#   "age": 30,
#   "skills": [
#     "Python",
#     "Django",
#     "PostgreSQL"
#   ],
#   "is_active": true,
#   "metadata": null
# }

Sorting Keys

sorted_json = json.dumps(data, indent=2, sort_keys=True)
print(sorted_json)
# {
#   "age": 30,
#   "is_active": true,
#   "metadata": null,
#   "name": "Alice",
#   "skills": [...]
# }

Controlling Whitespace with separators

For compact output (minified):

compact = json.dumps(data, separators=(",", ":"))
print(compact)
# {"name":"Alice","age":30,"skills":["Python","Django","PostgreSQL"],"is_active":true,"metadata":null}

File Read/Write

Writing JSON to a File

import json
 
data = {
    "users": [
        {"id": 1, "name": "Alice"},
        {"id": 2, "name": "Bob"},
    ],
    "metadata": {"version": "1.0", "exported": "2026-06-21"},
}
 
with open("output.json", "w", encoding="utf-8") as file:
    json.dump(data, file, indent=2, ensure_ascii=False)

Reading JSON from a File

import json
 
try:
    with open("output.json", "r", encoding="utf-8") as file:
        data = json.load(file)
    print(f"Loaded {len(data['users'])} users")
except FileNotFoundError:
    print("File not found")
except json.JSONDecodeError as e:
    print(f"Invalid JSON: {e}")

Handling Non-ASCII Characters

The ensure_ascii=False parameter preserves Unicode characters instead of escaping them:

data = {"name": "José", "city": "São Paulo"}
 
# Default: escapes non-ASCII
print(json.dumps(data))
# {"name": "Jos\u00e9", "city": "S\u00e3o Paulo"}
 
# With ensure_ascii=False
print(json.dumps(data, ensure_ascii=False, indent=2))
# {
#   "name": "José",
#   "city": "São Paulo"
# }

Error Handling

json.JSONDecodeError

Invalid JSON raises json.JSONDecodeError (a subclass of ValueError):

import json
 
def safe_loads(json_string):
    try:
        return json.loads(json_string)
    except json.JSONDecodeError as e:
        print(f"JSON parse error at line {e.lineno}, column {e.colno}: {e.msg}")
        print(f"Problematic text: {e.doc[max(0, e.pos-20):e.pos+20]}")
        return None
 
# Test with various errors
test_cases = [
    '{"name": "Alice",}',           # trailing comma
    "{'name': 'Alice'}",            # single quotes
    '{"name": "Alice"',             # missing closing brace
    '{"name": "Alice", "age": }',   # missing value
    '',                             # empty string
]
 
for test in test_cases:
    result = safe_loads(test)
    if result is None:
        print("→ Failed to parse\n")

Type Validation After Parsing

import json
 
def parse_object(json_string):
    try:
        data = json.loads(json_string)
        if not isinstance(data, dict):
            raise ValueError("Expected a JSON object (dict)")
        return data
    except (json.JSONDecodeError, ValueError) as e:
        print(f"Error: {e}")
        return {}
 
# Usage
data = parse_object('{"key": "value"}')  # OK
data = parse_object('[1, 2, 3]')         # Error: Expected a JSON object

Advanced Usage

Parsing Large Files with ijson

For very large JSON files that do not fit in memory, use the ijson library for streaming:

pip install ijson
import ijson
 
with open("huge-file.json", "r", encoding="utf-8") as f:
    # Stream objects one at a time
    for item in ijson.items(f, "item"):
        print(f"Processing record: {item['id']}")

Using JSON Pointer (RFC 6901)

The jsonpointer library lets you navigate JSON with pointer expressions:

pip install jsonpointer
import json
import jsonpointer
 
data = json.loads('''
{
  "users": [
    {"name": "Alice", "address": {"city": "NYC"}},
    {"name": "Bob", "address": {"city": "SF"}}
  ]
}
''')
 
city = jsonpointer.resolve_pointer(data, "/users/0/address/city")
print(city)  # NYC

json.tools Module

Python's json.tool module provides command-line JSON validation and pretty printing:

# Validate and pretty print
echo '{"name": "Alice"}' | python -m json.tool
 
# From a file
python -m json.tool input.json
 
# Minify output
python -m json.tool input.json --compact

Common Pitfalls

Trailing Commas

Python allows trailing commas in dicts/lists, but JSON does not:

# INVALID JSON — will raise json.JSONDecodeError
json.loads('{"name": "Alice", "age": 30,}')
 
# Fix: strip trailing comma before parsing
import re
 
def fix_json(json_string):
    return re.sub(r",(s*[}]])", r"\1", json_string)
 
cleaned = fix_json('{"name": "Alice", "age": 30,}')
data = json.loads(cleaned)
print(data)  # {'name': 'Alice', 'age': 30}

Single Quotes

JSON requires double quotes. Python's json module does not accept single quotes:

# INVALID — single quotes
json.loads("{'name': 'Alice'}")  # json.JSONDecodeError
 
# VALID
json.loads('{"name": "Alice"}')  # OK

Duplicate Keys

JSON does not strictly forbid duplicate keys, but behavior is undefined. Python keeps the last value:

data = json.loads('{"a": 1, "a": 2}')
print(data["a"])  # 2 (last value wins)

Non-JSON Serializable Types

Some Python types cannot be serialized to JSON:

import json
from datetime import datetime
 
# This will raise TypeError
try:
    json.dumps({"now": datetime.now()})
except TypeError as e:
    print(f"Cannot serialize: {e}")
 
# Fix: use a custom encoder or convert to string first
json.dumps({"now": datetime.now().isoformat()})  # OK

Performance Tips

1. Use json.loads() Over eval()

Never use eval() or ast.literal_eval() to parse JSON. json.loads() is faster, safer, and properly validates input.

2. Specify encoding Explicitly

Always specify encoding="utf-8" when opening JSON files to avoid platform-dependent behavior:

with open("data.json", "r", encoding="utf-8") as f:
    data = json.load(f)

3. Reuse File Handles

For multiple reads, keep the file handle open rather than reopening:

with open("data.json", "r", encoding="utf-8") as f:
    data1 = json.load(f)  # First parse
    # Note: you cannot json.load() twice from same stream
    # Reset to read again if needed:
    f.seek(0)
    data2 = json.load(f)

4. Use orjson for Extreme Performance

For high-throughput applications, consider orjson — a Rust-backed JSON library that is significantly faster:

pip install orjson
import orjson
 
data = orjson.loads(b'{"name": "Alice", "age": 30}')
print(data)  # {'name': 'Alice', 'age': 30}
 
# Serialize
json_bytes = orjson.dumps(data)
print(json_bytes)  # b'{"name":"Alice","age":30}'

5. Batch Processing

When processing many small JSON strings, batch them to reduce function call overhead:

import json
 
json_strings = ['{"id": 1}', '{"id": 2}', '{"id": 3}']
# Parse all at once
parsed = [json.loads(s) for s in json_strings]

LangStop JSON Tools

Related Tools

Try these complementary developer tools: