How to Serialize Python namedtuple to YAML

This tutorial will guide you through converting namedtuple instances to YAML format and back again.

You’ll learn how to overcome the default limitations of PyYAML when dealing with namedtuples, create custom representers and constructors, and handle various special cases.

 

 

Default Behavior of PyYAML with namedtuple

By default, PyYAML doesn’t recognize namedtuple objects and treats them like regular tuples.

This means that important information like field names is lost during serialization.

from collections import namedtuple
import yaml
Person = namedtuple('Person', ['name', 'age'])
amira = Person('Amira', 28)
print(yaml.dump(amira))

Output:

!!python/object:__main__.Person
- Amira
- 28

It doesn’t preserve the field names and uses a Python-specific tag.

 

Custom YAML representers for namedtuple

You can create a custom representer to solve that problem:

from collections import namedtuple
import yaml
Person = namedtuple('Person', ['name', 'age'])
def represent_namedtuple(dumper, data):
    return dumper.represent_mapping('tag:yaml.org,2002:map', data._asdict().items())
yaml.add_representer(Person, represent_namedtuple)
amira = Person('Amira', 28)
print(yaml.dump(amira))

Output:

name: Amira
age: 28

This custom representer converts the namedtuple to a dictionary.

 

Serialization methods

Convert namedtuple to dictionary

You can convert namedtuples to dictionaries before serialization:

from collections import namedtuple
import yaml
Person = namedtuple('Person', ['name', 'age'])
amira = Person('Amira', 28)
print(yaml.dump(amira._asdict()))

Output:

age: 28
name: Amira

This method is simple but doesn’t preserve the order of fields.

Preserve field names and order

To maintain field order, use an OrderedDict:

from collections import namedtuple, OrderedDict
import yaml
Person = namedtuple('Person', ['name', 'age'])
def represent_ordereddict(dumper, data):
    return dumper.represent_mapping('tag:yaml.org,2002:map', data.items())
yaml.add_representer(OrderedDict, represent_ordereddict)
amira = Person('Amira', 28)
print(yaml.dump(OrderedDict(amira._asdict())))

Output:

name: Amira
age: 28

This method preserves the field order in the YAML output.

Handle nested namedtuples

For nested namedtuples, create a recursive function:

from collections import namedtuple
import yaml
Person = namedtuple('Person', ['name', 'age'])
Address = namedtuple('Address', ['street', 'city', 'resident'])
def namedtuple_to_dict(obj):
    if isinstance(obj, tuple) and hasattr(obj, '_asdict'):
        return {key: namedtuple_to_dict(value) for key, value in obj._asdict().items()}
    elif isinstance(obj, (list, tuple)):
        return [namedtuple_to_dict(item) for item in obj]
    else:
        return obj
address = Address('123 Nile St', 'Cairo', Person('Amira', 28))
print(yaml.dump(namedtuple_to_dict(address)))

Output:

city: Cairo
resident:
  age: 28
  name: Amira
street: 123 Nile St

 

Deserialization methods

Custom YAML constructors for namedtuple

To deserialize YAML back into namedtuples, create custom constructors:

from collections import namedtuple
import yaml
Person = namedtuple('Person', ['name', 'age'])
def construct_yaml_person(loader, node):
  value = loader.construct_mapping(node)
  return Person(**value)
class CustomLoader(yaml.SafeLoader):
  pass

CustomLoader.add_constructor('!Person', construct_yaml_person)
yaml_string = """
!Person
name: Amira
age: 28
"""
print(yaml.load(yaml_string, Loader=CustomLoader))

Output:

Person(name='Amira', age=28)

This custom constructor allows you to recreate namedtuple objects from YAML data.

Recreate namedtuple from YAML data

You can also create a function to recreate namedtuples:

from collections import namedtuple
import yaml
def dict_to_namedtuple(d):
    for key, value in d.items():
        if isinstance(value, dict):
            d[key] = dict_to_namedtuple(value)
    return namedtuple('GenericNamedTuple', d.keys())(**d)
yaml_string = """
name: Amira
age: 28
address:
  street: 123 Nile St
  city: Cairo
"""
data = yaml.safe_load(yaml_string)
person = dict_to_namedtuple(data)
print(person)
print(person.address.street)

Output:

GenericNamedTuple(name='Amira', age=28, address=GenericNamedTuple(street='123 Nile St', city='Cairo'))
123 Nile St

This function recursively converts dictionaries to namedtuples.

 

Deal with default values in namedtuples

To handle namedtuples with default values, modify your serialization method by omitting fields with default values from the YAML output:

from collections import namedtuple
import yaml
Person = namedtuple('Person', ['name', 'age', 'city'], defaults=['Unknown'])
def represent_namedtuple(dumper, data):
    return dumper.represent_mapping('tag:yaml.org,2002:map', 
                                    {k: v for k, v in data._asdict().items() if v != data._field_defaults.get(k)})
yaml.add_representer(Person, represent_namedtuple)
amira = Person('Amira', 28)
print(yaml.dump(amira))

Output:

age: 28
name: Amira
Leave a Reply

Your email address will not be published. Required fields are marked *