Marky0
Marky0

Reputation: 2084

Deserialise a JSON string to nested objects using jsons

I am trying to deserialise a json string to an object using jsons but having problems with nested objects, but can't work out the syntax.

As an example the following code attempts to define the data structure as a series of dataclasses but fails to deserialise the nested objects C and D ? The syntax is clearly wrong, but its not clear to me how it should structured

import jsons
from dataclasses import dataclass

@dataclass
class D:
    E: str
class C:
    id: int
    name:str
@dataclass
class test:
    A: str
    B: int
    C: C()
    D: D()

jsonString = {"A":"a","B":1,"C":[{"id":1,"name":"one"},{"id":2,"name":"two"}],"D":[{"E":"e"}]}
instance = jsons.load(jsonString, test)

Can anyone indicate the correct way to deserialise the objects from json ?

Upvotes: 0

Views: 2221

Answers (4)

Evgeniy_Burdin
Evgeniy_Burdin

Reputation: 703

from dataclasses import dataclass
from typing import List

from validated_dc import ValidatedDC


@dataclass
class D(ValidatedDC):
    E: str


@dataclass
class C(ValidatedDC):
    id: int
    name: str


@dataclass
class Test(ValidatedDC):
    A: str
    B: int
    C: List[C]
    D: List[D]


jsonString = {
    "A": "a",
    "B": 1,
    "C": [{"id": 1, "name": "one"}, {"id": 2, "name": "two"}],
    "D": [{"E": "e"}]
}

instance = Test(**jsonString)

assert instance.C == [C(id=1, name='one'), C(id=2, name='two')]
assert instance.C[0].id == 1
assert instance.C[1].name == 'two'

assert instance.D == [D(E='e')]
assert instance.D[0].E == 'e'

ValidatedDC: https://github.com/EvgeniyBurdin/validated_dc

Upvotes: 1

chepner
chepner

Reputation: 531055

There are two relatively simple problems with your attempt:

  1. You forgot to decorate C with @dataclass.
  2. Test.C and Test.D aren't defined with types, but with instances of the types. (Further, you want both fields to be lists of the given type, not single instances of each.)

Given the code

import jsons
from dataclasses import dataclass
from typing import List


@dataclass
class D:
    E: str


@dataclass  # Problem 1 fixed
class C:
    id: int
    name: str


@dataclass
class Test:
    A: str
    B: int
    C: List[C]  # Problem 2 fixed; List[C] not C() or even C
    D: List[D]  # Problem 2 fixed; List[D], not D() or even D

Then

>>> obj = {"A":"a", "B":1, "C": [{"id": 1,"name": "one"}, {"id": 2, "name": "two"}], "D":[{"E": "e"}]}
>>> jsons.load(obj, Test)
test(A='a', B=1, C=[C(id=1, name='one'), C(id=2, name='two')], D=[D(E='e')])

Upvotes: 2

Marky0
Marky0

Reputation: 2084

I've finally managed to get this to work by removing the dataClass definition and expanding the class definitions old school.... code as follows...

import jsons

class D:
     def __init__(self, E = ""):
         self.E = E
class C:
    def __init__(self, id = 0, name=""):
        self.id = id
        self.name = name
class test:
    def __init__(self, A = "", B = 0, C = C(), D = D()):
        self.A = A
        self.B = B
        self.C = C
        self.D = D

jsonString = {"A":"a","B":1,"C":[{"id":1,"name":"one"},{"id":2,"name":"two"}],"D":[{"E":"e"}]}
instance = jsons.load(jsonString, test)

It now works but is not as clean as with a dataClass. Grateful if anyone can indicate how the original post can be constructed with the dataClass definition.

Upvotes: 0

You can do something like this:

from collections import namedtuple
# First parameter is the class/tuple name, second parameter
# is a space delimited string of varaibles.
# Note that the variable names should match the keys from 
# your dictionary of arguments unless only one argument is given.
A = namedtuple("A", "a_val") # Here the argument `a_val` can be called something else
B = namedtuple("B", "num")
C = namedtuple("C", "id name")
D = namedtuple("D", "E") # This must be `E` since E is the key in the dictionary.
# If you dont want immutable objects to can use full classes
# instead of namedtuples

# A dictionary which matches the name of an object seen in a payload
# to the object we want to create for that name.
object_options = {
  "A": A,
  "B": B,
  "C": C,
  "D": D
}
my_objects = [] # This is the list of object we get from the payload

jsonString = {"A":"a","B":1,"C":[{"id":1,"name":"one"},{"id":2,"name":"two"}],"D":[{"E":"e"}]}

for key, val in jsonString.items():
    if key in object_options: # If this is a valid object
        if isinstance(val, list): # If it is a list of this object
            for v in val: # Then we need to add each object in the list
                my_objects.append(object_options[key](**v))
        elif isinstance(val, dict): # If the object requires a dict then pass the whole dict as arugments
            my_objects.append(object_options[key](**val))
        else: # Else just add this object with a singular argument.
            my_objects.append(object_options[key](val))
print(my_objects)

Output:

[A(a_val='a'), B(num=1), C(id=1, name='one'), C(id=2, name='two'), D(E='e')]

Upvotes: 0

Related Questions