Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize a tree class object structure into json file format?

Given the code sample below, how can I serialize these class instances with JSON using Python 3?

class TreeNode():
    def __init__(self, name):
        self.name = name
        self.children = []

When I try to do a json.dumps I get the following error:

TypeError: <TreeNode object at 0x7f6sf4276f60> is not JSON serializable

I was then able to find that if I set the default to json.dumps to return a __dict__ I could serialize it fine but then doing a json.loads becomes an issue.

I can find a lot of custom encoder/decoder examples with basic strings but none where there is a list, in this case self.children. The children list will hold child nodes and their children other nodes. I need a way to get all of it.

like image 736
Biff Avatar asked May 11 '14 17:05

Biff


People also ask

How do I make an object JSON serializable?

Use toJSON() Method to make class JSON serializable So we don't need to write custom JSONEncoder. This new toJSON() serializer method will return the JSON representation of the Object. i.e., It will convert custom Python Object to JSON string.

How do you serialize an object to JSON in Python?

The json module exposes two methods for serializing Python objects into JSON format. dump() will write Python data to a file-like object. We use this when we want to serialize our Python data to an external JSON file. dumps() will write Python data to a string in JSON format.

What is JSON serializable object?

It is a format that encodes the data in string format. JSON is language independent and because of that, it is used for storing or transferring data in files. The conversion of data from JSON object string is known as Serialization and its opposite string JSON object is known as Deserialization.

Is JSON a serialization format?

JSON is a ubiquitous human-readable data serialization format that is supported by almost every popular programming language. JSON's data structures closely represent common objects in many languages, e.g. a Python dict can be represented by a JSON object , and a Python list by a JSON array .


1 Answers

Since you're dealing with a tree structure, it's natural to use nested dictionaries. The snippet of code below creates a subclass of dict and uses itself as the underlying __dict__ of the instance — which is an interesting and useful trick I've run across in many different contexts:

     Is it preferable to return an anonymous class or an object to use as a 'struct'? (stackoverflow)
     How to use a dot “.” to access members of dictionary? (stackoverflow)
     jsobject.py (PyDoc.net)
     Making Python Objects that act like Javascript Objects (James Robert's blog)
     AttrDict (ActiveState recipe)
     Dictionary with attribute-style access (ActiveState recipe)

…so often in fact, that I consider it to be a (less well-known) Python idiom.

class TreeNode(dict):
    def __init__(self, name, children=None):
        super().__init__()
        self.__dict__ = self
        self.name = name
        self.children = list(children) if children is not None else []

This solves half the serialization battle, but when the data produced is read back in with json.loads() it will be a regular dictionary object, not an instance of TreeNode. This is because JSONEncoder can encode dictionaries (and subclasses of them) itself.

One way to address that is add an alternative constructor method to the TreeNode class that can be called to reconstruct the data structure from the nested dictionary that json.loads() returns.

Here's what I mean:

    ...
    @staticmethod
    def from_dict(dict_):
        """ Recursively (re)construct TreeNode-based tree from dictionary. """
        node = TreeNode(dict_['name'], dict_['children'])
#        node.children = [TreeNode.from_dict(child) for child in node.children]
        node.children = list(map(TreeNode.from_dict, node.children))
        return node

if __name__ == '__main__':
    import json

    tree = TreeNode('Parent')
    tree.children.append(TreeNode('Child 1'))
    child2 = TreeNode('Child 2')
    tree.children.append(child2)
    child2.children.append(TreeNode('Grand Kid'))
    child2.children[0].children.append(TreeNode('Great Grand Kid'))

    json_str = json.dumps(tree, indent=2)
    print(json_str)
    print()
    pyobj = TreeNode.from_dict(json.loads(json_str))  # reconstitute
    print('pyobj class: {}'.format(pyobj.__class__.__name__))  # -> TreeNode
    print(json.dumps(pyobj, indent=2))

Output:

{
  "name": "Parent",
  "children": [
    {
      "name": "Child 1",
      "children": []
    },
    {
      "name": "Child 2",
      "children": [
        {
          "name": "Grand Kid",
          "children": [
            {
              "name": "Great Grand Kid",
              "children": []
            }
          ]
        }
      ]
    }
  ]
}

pyobj class: TreeNode
{
  "name": "Parent",
  "children": [
    {
      "name": "Child 1",
      "children": []
    },
    {
      "name": "Child 2",
      "children": [
        {
          "name": "Grand Kid",
          "children": [
            {
              "name": "Great Grand Kid",
              "children": []
            }
          ]
        }
      ]
    }
  ]
}
like image 187
martineau Avatar answered Nov 15 '22 14:11

martineau