0

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 ?

2
  • Does it need to be a dataclass or can you just use namedtuples. Commented Apr 22, 2019 at 16:57
  • I am not wedded to a dataClass (this is what jsons uses and is clean, but class C and D also have associated methods (which I haven't detailed in this example). I could extend a namedtuples but this then gets messy? Commented Apr 22, 2019 at 17:50

4 Answers 4

2

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')])
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks, #1 was a typo, but #2 was exactly what I was missing
Whilst I can understand why test.C needs to be added as a list as there are multiple instances, this is not the case for test.D as there is only one instance. For example, if I initialise test directly as follows instance = test(D=D(E="e")), test.D is defined as an object D not a List. Similarly, is there a type definition for test.D to avoid defining as a List ?
There's only one, but it's still a list of one item. If your Python dict could be {..., "D": {"E": "e"}} as well, you'll need to define D with a union type Union[D,List[D]].
Thanks, but Union[D,List[D]] still defines D as a List of one item not as a single object D of test
More precisely, the type hint provided to the dataclass doesn't affect what values you can assign to the field directly, but jsons does appear to take the type hints into account.
|
1
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

Comments

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')]

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.