JSON
Python
Data Conversion
Programming
Tutorial

How to convert JSON data into a Python object?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Python's json module converts JSON strings to Python dictionaries by default, but often you want to work with proper objects that have attribute access (obj.name) instead of dictionary access (obj["name"]). There are several approaches: using json.loads() with an object_hook, types.SimpleNamespace, dataclasses, Pydantic models, or the json.JSONDecoder class. The right choice depends on whether you need type validation, nested objects, or just simple attribute access.

Basic JSON to Dictionary

python
1import json
2
3json_string = '{"name": "Alice", "age": 30, "email": "[email protected]"}'
4
5# Default: returns a dictionary
6data = json.loads(json_string)
7print(data["name"])   # Alice
8print(type(data))     # <class 'dict'>

Method 1: SimpleNamespace with object_hook

types.SimpleNamespace provides attribute-style access with minimal boilerplate.

python
1import json
2from types import SimpleNamespace
3
4json_string = '{"name": "Alice", "age": 30, "address": {"city": "NYC", "zip": "10001"}}'
5
6# object_hook is called for every JSON object (including nested ones)
7obj = json.loads(json_string, object_hook=lambda d: SimpleNamespace(**d))
8
9print(obj.name)          # Alice
10print(obj.age)           # 30
11print(obj.address.city)  # NYC — nested objects work automatically
12
13# Convert back to dict
14print(vars(obj))
15# {'name': 'Alice', 'age': 30, 'address': namespace(city='NYC', zip='10001')}

Method 2: Custom Class with object_hook

python
1import json
2
3class User:
4    def __init__(self, name, age, email=None, **kwargs):
5        self.name = name
6        self.age = age
7        self.email = email
8        # Store unexpected fields
9        for key, value in kwargs.items():
10            setattr(self, key, value)
11
12    def __repr__(self):
13        return f"User(name={self.name!r}, age={self.age})"
14
15json_string = '{"name": "Alice", "age": 30, "email": "[email protected]"}'
16
17user = json.loads(json_string, object_hook=lambda d: User(**d))
18print(user)        # User(name='Alice', age=30)
19print(user.email)  # [email protected]

Method 3: Dataclasses (Python 3.7+)

python
1import json
2from dataclasses import dataclass
3
4@dataclass
5class Address:
6    city: str
7    zip: str
8
9@dataclass
10class User:
11    name: str
12    age: int
13    email: str
14    address: Address = None
15
16json_string = '''
17{
18    "name": "Alice",
19    "age": 30,
20    "email": "[email protected]",
21    "address": {"city": "NYC", "zip": "10001"}
22}
23'''
24
25data = json.loads(json_string)
26
27# Manual nested conversion
28address = Address(**data.pop("address")) if "address" in data else None
29user = User(**data, address=address)
30
31print(user)              # User(name='Alice', age=30, ...)
32print(user.address.city) # NYC

Pydantic provides automatic type coercion, validation, and nested model parsing.

python
1from pydantic import BaseModel
2
3class Address(BaseModel):
4    city: str
5    zip: str
6
7class User(BaseModel):
8    name: str
9    age: int
10    email: str
11    address: Address | None = None
12
13json_string = '{"name": "Alice", "age": "30", "email": "[email protected]", "address": {"city": "NYC", "zip": "10001"}}'
14
15# Pydantic parses JSON, coerces types, validates, and builds nested objects
16user = User.model_validate_json(json_string)
17
18print(user.name)          # Alice
19print(user.age)           # 30 (coerced from string "30" to int)
20print(user.address.city)  # NYC
21
22# Convert back to JSON
23print(user.model_dump_json())
24
25# Validation error example
26try:
27    bad = User.model_validate_json('{"name": "Bob"}')
28except Exception as e:
29    print(e)  # age field required, email field required

Method 5: json.JSONDecoder Subclass

python
1import json
2from types import SimpleNamespace
3
4class ObjectDecoder(json.JSONDecoder):
5    def __init__(self):
6        super().__init__(object_hook=self._decode_object)
7
8    def _decode_object(self, d):
9        return SimpleNamespace(**d)
10
11decoder = ObjectDecoder()
12obj = decoder.decode('{"name": "Alice", "items": [1, 2, 3]}')
13print(obj.name)   # Alice
14print(obj.items)  # [1, 2, 3]

Reading JSON from Files

python
1import json
2from types import SimpleNamespace
3
4# From file
5with open("data.json", "r") as f:
6    obj = json.load(f, object_hook=lambda d: SimpleNamespace(**d))
7
8# From URL
9import urllib.request
10with urllib.request.urlopen("https://api.example.com/user/1") as response:
11    obj = json.loads(response.read(), object_hook=lambda d: SimpleNamespace(**d))

Common Pitfalls

  • Nested objects not converted: A simple User(**data) only converts the top level. Nested JSON objects remain as dicts. Use object_hook (which recurses automatically) or manually convert nested structures.
  • JSON keys that are not valid Python identifiers: JSON keys like "first-name" or "class" cannot be used as Python attributes directly. SimpleNamespace allows getattr(obj, "first-name") but not obj.first-name. Use __dict__ access or rename keys during parsing.
  • Type mismatches with dataclasses: Dataclasses do not coerce types — passing a string "30" to an int field stores the string without error. Use Pydantic if you need automatic type coercion and validation.
  • object_hook called for every nested object: The object_hook function is called bottom-up for every JSON object, including nested ones. If your hook creates a specific class (e.g., User), nested objects will also be passed to the same hook, causing errors. Check the dict keys inside the hook to determine which class to instantiate.
  • Losing JSON arrays as Python lists: object_hook only transforms JSON objects (curly braces), not JSON arrays (square brackets). Arrays are converted to Python lists with their elements processed individually. This is usually correct but can be surprising if you expect arrays to become custom objects.

Summary

  • json.loads() returns dicts by default — use object_hook to convert to objects
  • SimpleNamespace with object_hook is the simplest approach for attribute access on nested JSON
  • Dataclasses provide structure but require manual nested conversion
  • Pydantic provides automatic validation, type coercion, and nested parsing — best for production APIs
  • object_hook is called recursively for all nested JSON objects automatically

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.