How to load JSON data into nested classes?

json, json-deserialization, python

Solution

You can identify objects by looking at their keys. You can then map them to the appropriate class.

Using your example data:

class AddressClass:
    # The parameters to init needs to be the same as the json keys
    def __init__(self, House_Number, Street_Number, State):
        self.house_number = House_Number
        self.street_number = Street_Number
        self.state = State

class EmployeeClass:
    # Same here
    def __init__(self, Name, Address):
        self.name = Name
        self.address = Address

# Map keys to classes
mapping = {frozenset(('House_Number', 
                      'Street_Number', 
                      'State')): AddressClass,
           frozenset(('Name', 
                      'Address')): EmployeeClass}

Then, create a function that converts a dictionary to an appropriate python class. This will be passed to `json.load` as `object_hook`:

def class_mapper(d):
    return mapping[frozenset(d.keys())](**d)

Above, `frozenset` is used because dict keys in the json are unordered (hence the set) and the dict keys in the mapping needs to be hashable (hence "frozen").

Finally, parse the json with the `class_mapper` function as `object_hook`:

j = '''
{
    "Address": {
        "House_Number": 2,
        "State": "MA",
        "Street_Number": 13
    },
    "Name": "John"
}
'''
employee = json.loads(j, object_hook=class_mapper)
print(employee.name,
      employee.address.house_number,
      employee.address.street_number,
      employee.address.state)
# John 2 13 MA

Additions

If your json data have optional keys, you can create a more robust `class_mapper`, although it may be slightly slower. You also need to add default values to the class constructor parameters that are optional.

def class_mapper(d):
    for keys, cls in mapping.items():
        if keys.issuperset(d.keys()):
            return cls(**d)
    else:
        # Raise exception instead of silently returning None
        raise ValueError('Unable to find a matching class for object: {!s}'.format(d))

Problem

I have JSON data as follows: ``` { "Address": { "House_Number": 2, "State": "MA", "Street_Number": 13 }, "Name": "John" } ``` I want to load it into a class defined as follows: ``` class Address: def __init__(self): self.House_Number = 0 class Employee: def __init__(self): self.Name = '' self.Address = Address() ``` If I use the class `Employee` as `object_hook`, then it uses the same class for both the objects (the outer object having `Name` and `Address` as members and inner object having members `House_Number` etc.). Basically, if `e` is the object in which the JSON data has been loaded, then `type(e.Address)` should be `Address` not `Employee`. Is there any way to load this JSON data into `Employee` class maintaining the class hierarchy? The hierarchy can be arbitrarily deep.

Original source