1

When writing APIs I often need to initialize classes from nested JSONs like this

# When my API receives such a ...
n_json = {"nested1": {"nested2" : { "nested3" : "..." }}}

# .. I need to instantiate a proper object out of it ...
nested1 = Nested1.from_json(json.loads(n_json))

# ... so I can later use the instance in OOP style.
nested1.nested2.run_an_intance_method()

In my code this results in cascades of

class Nested1:
    @classmethod
    def from_json(json):
        self.nested2=Nested2.from_json(json.nested2)

With every layer of nesting this gets more repetetive and error prone. Any clever way to do this?

Iwan LD
  • 322
  • 4
  • 13
  • show a real example. Don't understand, what you are trying. – Daniel Aug 25 '16 at 18:00
  • @Daniel Clearer? Still trying to find the right balance. In [this post](http://stackoverflow.com/questions/39097411/how-to-reduce-boilerplate-when-initializating-classes-from-jsons-in-python-3-5) I explained my motivation for this question --- but obviously too lengthy/opinionated ;) – Iwan LD Aug 25 '16 at 19:10

1 Answers1

1

This is how you can create a nested object from JSON (similarly to how PHP does that). To create object tree from JSON (rather, parsed JSON, which is just a Python structure), write the following class:

class Nested:
    def __new__(cls, structure):
        self = super(Nested, cls).__new__(cls)
        if type(structure) is dict:
            self.__dict__ = {key: Nested(structure[key]) for key in structure}
        elif type(structure) is list:
            self = [Nested(item) for item in structure]
        else:
            self = structure
        return self

and call it from your parsed JSON. For example:

import json

s = json.dumps({'a': 1, 'b': {'c': [1, 2, {'d': 3}], 'e': {'f': 'g'}}})

result = Nested(json.loads(s))

print(result.a)  #  1

print(result.b.c) # [1, 2, <__main__.Nested object at 0x00000297A983D828>]

print(result.b.c[0]) # 1

print(result.b.c[2].d) # 3

Here is what is how the class works:

  1. __new__ is invoked before anything else. Here we construct an instance of an object (empty)

  2. If new is list, we replace self with list of nested objects, so that Nested({'a': [1, 2, 3]).a is equal to [Nested(1), Nested(2), Nested(3)] (which also equals to just [1, 2, 3], see number 4)

  3. If new is dict, for each key we create a property with the same name and assign Nested(dict[key]) to it.

  4. If any other type, just assign value to self (replace self with value) so that Nested({'a': ['b', 'c', 'd']}).a[0] equals 'b', not Nested(..)

Dmitry Torba
  • 3,004
  • 1
  • 14
  • 24
  • Thx, looks already nicer than my code which does similar things :) I just enhanced the question to emphasise that Nested1 and Nested2 are *different* classes, possibly with instance methods and such. Any ideas how one could incorporate that? (Or one should not try this...) – Iwan LD Aug 25 '16 at 19:23
  • 1
    @IwanLD You really should not, at this point you are trying to so something very hacky. If you need to store nested different classes using JSON, you just have to define to_json and from_json on each on them – Dmitry Torba Aug 25 '16 at 20:36
  • Yes, I also feel it might be too higher ordery. However using `'self.__dict__' might be viewed this way. As always: It depends. In current situation: I want to generate a nested object oriented DSL from JSON. Guess I don' have much choices but going full functional ;) But it made me think: most classes in my APIs are just nested Datastructures + methods. Why not reduce the datastructure boilerplate to only the essential part - the signature? – Iwan LD Aug 27 '16 at 11:34
  • This is excellent. What would the process be to export back to JSON? – Adam Erickson Dec 04 '17 at 21:45