Skip to content

How to Convert a NamedTuple to a Dictionary in Python

Learn how to convert Python NamedTuple objects to dictionaries using _asdict(), handle nested structures, and serialize to JSON for API compatibility.

NamedTuples provide memory-efficient, readable data structures with named field access, but JSON serialization, APIs, and many libraries require standard dictionaries. Converting between the two is straightforward using the built-in _asdict() method.

In this guide, you will learn how to convert NamedTuples to dictionaries, handle nested structures, serialize to JSON, and perform selective field conversion.

Using _asdict() (Official Method)

Every NamedTuple instance has a built-in _asdict() method that returns a standard dictionary:

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(10, 20)

data = p._asdict()

print(data)
print(type(data))

Output:

{'x': 10, 'y': 20}
<class 'dict'>

Class-Based NamedTuple (Modern Python)

With typing.NamedTuple (Python 3.6+), which supports type annotations, the same _asdict() method works identically:

from typing import NamedTuple

class User(NamedTuple):
    id: int
    name: str
    email: str

user = User(1, "Alice", "alice@example.com")

print(user._asdict())

Output:

{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}

With Default Values

from typing import NamedTuple

class Config(NamedTuple):
    host: str = "localhost"
    port: int = 8080
    debug: bool = False

config = Config(host="192.168.1.1")
print(config._asdict())

Output:

{'host': '192.168.1.1', 'port': 8080, 'debug': False}

Converting a List of NamedTuples

A common scenario is serializing collections of NamedTuples, such as database query results:

import json
from collections import namedtuple

User = namedtuple("User", ["id", "name", "email"])

users = [
    User(1, "Alice", "alice@example.com"),
    User(2, "Bob", "bob@example.com"),
    User(3, "Charlie", "charlie@example.com")
]

user_dicts = [u._asdict() for u in users]

json_str = json.dumps(user_dicts, indent=2)
print(json_str)

Output:

[
  {
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com"
  },
  {
    "id": 2,
    "name": "Bob",
    "email": "bob@example.com"
  },
  {
    "id": 3,
    "name": "Charlie",
    "email": "charlie@example.com"
  }
]

Selective Field Conversion

Filtering Out Sensitive Fields

from typing import NamedTuple

class Employee(NamedTuple):
    id: int
    name: str
    salary: float
    ssn: str  # Sensitive field

emp = Employee(1, "Alice", 75000.0, "123-45-6789")

public_data = {k: v for k, v in emp._asdict().items() if k != "ssn"}
print(public_data)

Output:

{'id': 1, 'name': 'Alice', 'salary': 75000.0}

Renaming Fields During Conversion

When internal field names do not match the expected API output:

from typing import NamedTuple

class DBRecord(NamedTuple):
    user_id: int
    user_name: str

record = DBRecord(1, "Alice")

field_map = {"user_id": "id", "user_name": "name"}
api_data = {field_map.get(k, k): v for k, v in record._asdict().items()}

print(api_data)

Output:

{'id': 1, 'name': 'Alice'}

The field_map.get(k, k) call uses the mapped name if one exists, otherwise falls back to the original name.

Handling Nested NamedTuples

When NamedTuples contain other NamedTuples as fields, _asdict() only converts the top level. A recursive function handles the full depth:

from typing import NamedTuple

class Address(NamedTuple):
    city: str
    zip_code: str

class Person(NamedTuple):
    name: str
    address: Address

def namedtuple_to_dict(obj):
    """Recursively convert NamedTuples to dictionaries."""
    if hasattr(obj, '_asdict'):
        return {k: namedtuple_to_dict(v) for k, v in obj._asdict().items()}
    elif isinstance(obj, list):
        return [namedtuple_to_dict(item) for item in obj]
    else:
        return obj

person = Person("Alice", Address("New York", "10001"))

print(person._asdict())

print(namedtuple_to_dict(person))

Output:

{'name': 'Alice', 'address': Address(city='New York', zip_code='10001')}
{'name': 'Alice', 'address': {'city': 'New York', 'zip_code': '10001'}}

JSON Serialization with a Custom Encoder

For seamless JSON serialization without manually calling _asdict() each time, create a custom encoder:

import json
from typing import NamedTuple

class Product(NamedTuple):
    id: int
    name: str
    price: float

def convert_namedtuples(obj):
    if hasattr(obj, '_asdict'):
        return obj._asdict()
    elif isinstance(obj, list):
        return [convert_namedtuples(item) for item in obj]
    elif isinstance(obj, dict):
        return {key: convert_namedtuples(value) for key, value in obj.items()}
    return obj

class NamedTupleEncoder(json.JSONEncoder):
    def encode(self, obj):
        obj = convert_namedtuples(obj)
        return super().encode(obj)
    
    def default(self, obj):
        if hasattr(obj, '_asdict'):
            return obj._asdict()
        return super().default(obj)

product = Product(1, "Widget", 29.99)

print(json.dumps(product, cls=NamedTupleEncoder))

products = [Product(1, "Widget", 10.0), Product(2, "Gadget", 20.0)]
print(json.dumps(products, cls=NamedTupleEncoder, indent=2))

Output:

{"id": 1, "name": "Widget", "price": 29.99}
[
  {
    "id": 1,
    "name": "Widget",
    "price": 10.0
  },
  {
    "id": 2,
    "name": "Gadget",
    "price": 20.0
  }
]

Alternative: Using zip() with _fields

An alternative approach pairs the _fields attribute with the tuple values using zip():

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(10, 20)

data = dict(zip(p._fields, p))
print(data)

data = p._asdict()
print(data)

Output:

{'x': 10, 'y': 20}
{'x': 10, 'y': 20}

Both produce the same result, but _asdict() is more readable and self-documenting.

Reverse Operation: Dictionary to NamedTuple

To convert a dictionary back to a NamedTuple, use the ** unpacking operator:

from typing import NamedTuple

class User(NamedTuple):
    id: int
    name: str

data = {"id": 1, "name": "Alice"}
user = User(**data)
print(user)

data_extra = {"id": 1, "name": "Alice", "extra": "ignored"}
user = User(**{k: v for k, v in data_extra.items() if k in User._fields})
print(user)

Output:

User(id=1, name='Alice')
User(id=1, name='Alice')

The filtering step is important because passing unexpected keyword arguments to a NamedTuple constructor raises a TypeError.

Practical Example: API Response Builder

A complete example combining nested NamedTuples, recursive conversion, and JSON serialization:

from typing import NamedTuple, List
import json

class OrderItem(NamedTuple):
    product_id: int
    quantity: int
    price: float

class Order(NamedTuple):
    order_id: int
    customer: str
    items: List[OrderItem]

def to_api_response(obj):
    """Convert nested NamedTuples to an API-friendly dictionary."""
    if hasattr(obj, '_asdict'):
        return {k: to_api_response(v) for k, v in obj._asdict().items()}
    elif isinstance(obj, list):
        return [to_api_response(item) for item in obj]
    else:
        return obj

order = Order(
    order_id=1001,
    customer="Alice",
    items=[
        OrderItem(1, 2, 29.99),
        OrderItem(2, 1, 49.99)
    ]
)

response = to_api_response(order)
print(json.dumps(response, indent=2))

Output:

{
  "order_id": 1001,
  "customer": "Alice",
  "items": [
    {
      "product_id": 1,
      "quantity": 2,
      "price": 29.99
    },
    {
      "product_id": 2,
      "quantity": 1,
      "price": 49.99
    }
  ]
}

Performance Comparison

import timeit
from collections import namedtuple

Point = namedtuple("Point", ["x", "y", "z"])
p = Point(1, 2, 3)

def with_asdict():
    return p._asdict()

def with_manual():
    return {"x": p.x, "y": p.y, "z": p.z}

def with_zip():
    return dict(zip(p._fields, p))

print(f"_asdict():  {timeit.timeit(with_asdict, number=100000):.4f}s")
print(f"Manual:     {timeit.timeit(with_manual, number=100000):.4f}s")
print(f"zip():      {timeit.timeit(with_zip, number=100000):.4f}s")

Typical output:

_asdict():  0.0234s
Manual:     0.0156s
zip():      0.0345s

Manual construction is marginally faster for simple cases, but _asdict() is preferred because it automatically adapts when fields are added or removed from the NamedTuple definition.

Quick Reference

Scenario Method
Single NamedTuple obj._asdict()
List of NamedTuples [x._asdict() for x in items]
Nested structures Recursive conversion function
JSON serialization Custom encoder or pre-convert with _asdict()
Selective fields Dict comprehension with filtering
Field renaming Dict comprehension with a field mapping
Dict to NamedTuple MyTuple(**data)

Conclusion

Converting NamedTuples to dictionaries in Python is straightforward thanks to the built-in _asdict() method. For flat NamedTuples, a single _asdict() call is all you need. For collections, a list comprehension like [x._asdict() for x in items] handles the conversion cleanly. For nested NamedTuples, a small recursive helper function ensures every level is fully converted.

Navigation