Dynamically create Python object from deserialized JSON using recursion (descriptor protocol?)

Viewed 143

I want a function to create a Python object with from a deserialized JSON object (DSO). The function must support recursion. If an item in the DSO is a dictionary, then dynamically create an associated property for each key in the dictionary. If an item is a sequence (list/tuple/set), then create an object that supports indexing, ie. obj[val].

I have the first part of this requirement working fine. I'm not sure what is the most elegant way to approach dealing with sequences and dynamically creating a __getitem__ method on the fly.

Here's my code:

def to_obj(item):
    '''Create object from deserialized JSON.

    This function calls itself recursively. If item is a {}, then an
    object that mirrors the dictionary is returned. Otherwise, just the item
    itself is returned.

    :param item: <object>.
    :returns: <object>.
    '''
    if isinstance(item, dict):
        obj = type('__object', (object,), {})

        for key, value in item.items():
            setattr(obj, key, to_obj(value))

        return obj

    else:
        return item

This transforms this:

artifact = {
    "k1": "value1",
    "k2": {
        "k2key1": "k2value1",
        "k2key2": {
            "k2key2key1": "k2key2value1",
        }
    }
}

into this:

obj = to_obj(artifact)
obj.k2.k2key2.k2key2key1 # returns "k2key2value1"

However, sometimes the value of some of the {} items are sequences, like so:

artifact = {
    "k1": "value1",
    "k2": {
        "k2key1": "k2value1",
        "k2key2": [
            {
                "item0key1": "item0value1",
                "item0key2": "item0value2"
            },

            {
                "item1key1": "item1value1",
                "item1key2": "item1value2"
            }
        ]
    }
}

I want the recursion to work in the same way for sequences that may contain other dictionaries, etc - so I need code that supports the following. Note the element lookup on k2key2.

obj = to_obj(artifact)
obj.k2.k2key2[1].item1key2 # returns "item1value2"

I saw an SO post by @MartijnPieters mentioning the descriptor protocol as an elegant way to approach this, but exactly how to do this correctly I do not know.

Any help would be appreciated!

1 Answers
def toObj(item):
    if isinstance(item, dict):
        obj = type('__object', (object,), {})

        for key, value in item.items():
            print(key, value)
            setattr(obj, key, toObj(value))

        return obj
    elif isinstance(item, list):
        // you can use map function if you want to be more elegant, its also faster
        l = []
        for i in item:
            l.append(toObj(i))
        return l
    else:
        return item

i think its prity straight forward (excuse my grammar)

Related