16.6. JSON Object

16.6.1. SetUp

>>> from pprint import pprint
>>> from dataclasses import dataclass
>>> import json

16.6.2. Object Encoder

>>> class User:
...     def __init__(self, username, password, groups=None):
...         self.username = username
...         self.password = password
...         self.groups = groups if groups else []
...
...     def __repr__(self):
...         clsname = self.__class__.__qualname__
...         arguments = ', '.join(f'{k}={v!r}' for k,v in vars(self).items())
...         return f'{clsname}({arguments})'
>>>
>>>
>>> def encoder(obj):
...     return vars(obj)
>>>
>>>
>>> DATA = User('alice', 'secret', groups=['users', 'staff'])
>>>
>>> result = json.dumps(DATA, default=encoder)
>>> print(result)
{"username": "alice", "password": "secret", "groups": ["users", "staff"]}

16.6.3. Object Decoder

>>> class User:
...     def __init__(self, username, password, groups=None):
...         self.username = username
...         self.password = password
...         self.groups = groups if groups else []
...
...     def __repr__(self):
...         clsname = self.__class__.__qualname__
...         arguments = ', '.join(f'{k}={v!r}' for k,v in vars(self).items())
...         return f'{clsname}({arguments})'
>>>
>>>
>>> def decoder(data):
...     return User(**data)
>>>
>>>
>>> DATA = """{
...   "username": "alice",
...   "password": "secret",
...   "groups": ["users", "staff"]
... }"""
>>>
>>> result =  json.loads(DATA, object_hook=decoder)
>>> print(result)
User(username='alice', password='secret', groups=['users', 'staff'])

16.6.4. Encode Object with Relation

>>> class User:
...     def __init__(self, firstname, lastname, age=None, groups=None):
...         self.firstname = firstname
...         self.lastname = lastname
...         self.age = age
...         self.groups = groups if groups else []
...
...     def __repr__(self):
...         clsname = self.__class__.__qualname__
...         arguments = ', '.join(f'{k}={v!r}' for k,v in vars(self).items())
...         return f'{clsname}({arguments})'
>>>
>>> class Group:
...     def __init__(self, name):
...         self.name = name
...
...     def __repr__(self):
...         return f'{self.name}'
>>>
>>>
>>> DATA = [
...     User(firstname='Alice', lastname='Apricot', age=30, groups=[
...         Group('users'),
...         Group('staff'),
...     ]),
...
...     User(firstname='Bob', lastname='Blackthorn', age=31, groups=[
...         Group('users'),
...         Group('staff'),
...     ]),
...
...     User(firstname='Carol', lastname='Corn', age=32, groups=[
...         Group('users'),
...     ]),
...
...     User(firstname='Dave', lastname='Durian', age=33, groups=[
...         Group('users'),
...     ]),
...
...     User(firstname='Eve', lastname='Elderberry', age=34, groups=[
...         Group('users'),
...         Group('staff'),
...         Group('admins'),
...     ]),
...
...     User(firstname='Mallory', lastname='Melon', age=15, groups=[]),
... ]
>>>
>>>
>>> def encoder(obj):
...     data = {'__clsname__': obj.__class__.__name__}
...     return data | vars(obj)
>>>
>>>
>>> result = json.dumps(DATA, default=encoder, indent=2)
>>> print(result)
[
  {
    "__clsname__": "User",
    "firstname": "Alice",
    "lastname": "Apricot",
    "age": 30,
    "groups": [
      {
        "__clsname__": "Group",
        "name": "users"
      },
      {
        "__clsname__": "Group",
        "name": "staff"
      }
    ]
  },
  {
    "__clsname__": "User",
    "firstname": "Bob",
    "lastname": "Blackthorn",
    "age": 31,
    "groups": [
      {
        "__clsname__": "Group",
        "name": "users"
      },
      {
        "__clsname__": "Group",
        "name": "staff"
      }
    ]
  },
  {
    "__clsname__": "User",
    "firstname": "Carol",
    "lastname": "Corn",
    "age": 32,
    "groups": [
      {
        "__clsname__": "Group",
        "name": "users"
      }
    ]
  },
  {
    "__clsname__": "User",
    "firstname": "Dave",
    "lastname": "Durian",
    "age": 33,
    "groups": [
      {
        "__clsname__": "Group",
        "name": "users"
      }
    ]
  },
  {
    "__clsname__": "User",
    "firstname": "Eve",
    "lastname": "Elderberry",
    "age": 34,
    "groups": [
      {
        "__clsname__": "Group",
        "name": "users"
      },
      {
        "__clsname__": "Group",
        "name": "staff"
      },
      {
        "__clsname__": "Group",
        "name": "admins"
      }
    ]
  },
  {
    "__clsname__": "User",
    "firstname": "Mallory",
    "lastname": "Melon",
    "age": 15,
    "groups": []
  }
]

16.6.5. Decode

Encoding nested objects with relations to JSON:

>>> class User:
...     def __init__(self, firstname, lastname, age=None, groups=None):
...         self.firstname = firstname
...         self.lastname = lastname
...         self.age = age
...         self.groups = groups if groups else []
...
...     def __repr__(self):
...         clsname = self.__class__.__qualname__
...         arguments = ', '.join(f'{k}={v!r}' for k,v in vars(self).items())
...         return f'{clsname}({arguments})'
>>>
>>> class Group:
...     def __init__(self, name):
...         self.name = name
...
...     def __repr__(self):
...         return f'{self.name}'
>>>
>>>
>>> DATA = (
...     '[{"__clsname__": "User", "firstname": "Alice", "lastname": "Apricot", '
...     '"age": 30, "groups": [{"__clsname__": "Group", "name": "users"}, '
...     '{"__clsname__": "Group", "name": "staff"}]}, {"__clsname__": "User", '
...     '"firstname": "Bob", "lastname": "Blackthorn", "age": 31, "groups": '
...     '[{"__clsname__": "Group", "name": "users"}, {"__clsname__": "Group", '
...     '"name": "staff"}]}, {"__clsname__": "User", "firstname": "Carol", '
...     '"lastname": "Corn", "age": 32, "groups": [{"__clsname__": "Group", '
...     '"name": "users"}]}, {"__clsname__": "User", "firstname": "Dave", '
...     '"lastname": "Durian", "age": 33, "groups": [{"__clsname__": "Group", '
...     '"name": "users"}]}, {"__clsname__": "User", "firstname": "Eve", '
...     '"lastname": "Elderberry", "age": 34, "groups": [{"__clsname__": '
...     '"Group", "name": "users"}, {"__clsname__": "Group", "name": "staff"}, '
...     '{"__clsname__": "Group", "name": "admins"}]}, {"__clsname__": "User", '
...     '"firstname": "Mallory", "lastname": "Melon", "age": 15, "groups": '
...     '[]}]'
... )
>>>
>>>
>>> def decoder(obj):
...     clsname = obj.pop('__clsname__')
...     cls = globals()[clsname]
...     return cls(**obj)
>>>
>>>
>>> result = json.loads(DATA, object_hook=decoder)
>>> pprint(result)
[User(firstname='Alice', lastname='Apricot', age=30, groups=[users, staff]),
 User(firstname='Bob', lastname='Blackthorn', age=31, groups=[users, staff]),
 User(firstname='Carol', lastname='Corn', age=32, groups=[users]),
 User(firstname='Dave', lastname='Durian', age=33, groups=[users]),
 User(firstname='Eve', lastname='Elderberry', age=34, groups=[users, staff, admins]),
 User(firstname='Mallory', lastname='Melon', age=15, groups=[])]

16.6.6. Class Encoder and Decoder

>>> import json
>>> from pprint import pprint
>>>
>>>
>>> class User:
...     def __init__(self, firstname, lastname, age=None, groups=None):
...         self.firstname = firstname
...         self.lastname = lastname
...         self.age = age
...         self.groups = groups if groups else []
...
...     def __repr__(self):
...         clsname = self.__class__.__qualname__
...         arguments = ', '.join(f'{k}={v!r}' for k,v in vars(self).items())
...         return f'{clsname}({arguments})'
>>>
>>> class Group:
...     def __init__(self, name):
...         self.name = name
...
...     def __repr__(self):
...         return f'{self.name}'
>>>
>>>
>>> DATA = [
...     User(firstname='Alice', lastname='Apricot', age=30, groups=[
...         Group('users'),
...         Group('staff'),
...     ]),
...
...     User(firstname='Bob', lastname='Blackthorn', age=31, groups=[
...         Group('users'),
...         Group('staff'),
...     ]),
...
...     User(firstname='Carol', lastname='Corn', age=32, groups=[
...         Group('users'),
...     ]),
...
...     User(firstname='Dave', lastname='Durian', age=33, groups=[
...         Group('users'),
...     ]),
...
...     User(firstname='Eve', lastname='Elderberry', age=34, groups=[
...         Group('users'),
...         Group('staff'),
...         Group('admins'),
...     ]),
...
...     User(firstname='Mallory', lastname='Melon', age=15, groups=[]),
... ]
>>> class Encoder(json.JSONEncoder):
...     def default(self, obj):
...         data = vars(obj)
...         data['__clsname__'] = obj.__class__.__name__
...         return data
>>>
>>>
>>> result = json.dumps(DATA, cls=Encoder)
>>> pprint(result, width=72)
('[{"firstname": "Alice", "lastname": "Apricot", "age": 30, "groups": '
 '[{"name": "users", "__clsname__": "Group"}, {"name": "staff", '
 '"__clsname__": "Group"}], "__clsname__": "User"}, {"firstname": '
 '"Bob", "lastname": "Blackthorn", "age": 31, "groups": [{"name": '
 '"users", "__clsname__": "Group"}, {"name": "staff", "__clsname__": '
 '"Group"}], "__clsname__": "User"}, {"firstname": "Carol", '
 '"lastname": "Corn", "age": 32, "groups": [{"name": "users", '
 '"__clsname__": "Group"}], "__clsname__": "User"}, {"firstname": '
 '"Dave", "lastname": "Durian", "age": 33, "groups": [{"name": '
 '"users", "__clsname__": "Group"}], "__clsname__": "User"}, '
 '{"firstname": "Eve", "lastname": "Elderberry", "age": 34, "groups": '
 '[{"name": "users", "__clsname__": "Group"}, {"name": "staff", '
 '"__clsname__": "Group"}, {"name": "admins", "__clsname__": '
 '"Group"}], "__clsname__": "User"}, {"firstname": "Mallory", '
 '"lastname": "Melon", "age": 15, "groups": [], "__clsname__": '
 '"User"}]')
>>> class Decoder(json.JSONDecoder):
...     def __init__(self):
...         super().__init__(object_hook=self.default)
...
...     def default(self, data: dict) -> dict:
...         clsname = data.pop('__clsname__')
...         cls = globals()[clsname]
...         return cls(**data)
>>>
>>>
>>> result = json.loads(result, cls=Decoder)
>>> pprint(result)
[User(firstname='Alice', lastname='Apricot', age=30, groups=[users, staff]),
 User(firstname='Bob', lastname='Blackthorn', age=31, groups=[users, staff]),
 User(firstname='Carol', lastname='Corn', age=32, groups=[users]),
 User(firstname='Dave', lastname='Durian', age=33, groups=[users]),
 User(firstname='Eve', lastname='Elderberry', age=34, groups=[users, staff, admins]),
 User(firstname='Mallory', lastname='Melon', age=15, groups=[])]

16.6.7. Assignments

# %% About
# - Name: JSON Object Dumps
# - Difficulty: medium
# - Lines: 5
# - Minutes: 5

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% English
# 1. Serialize data from variable `DATA`
# 2. To each row add `__clsname__` with an object type name
# 3. Use `json` module
# 4. Result write to variable `result`
# 5. Run doctests - all must succeed

# %% Polish
# 1. Zserializuj dane ze zmiennej `DATA`
# 2. To each row add `__clsname__` with an object type name
# 3. Użyj modułu `json`
# 4. Wynik zapisz do zmiennej `result`
# 5. Uruchom doctesty - wszystkie muszą się powieść

# %% Example
# >>> result
# ('[{"__clsname__": "User", "firstname": "Alice", "lastname": "Apricot", "age": 30, "groups": []}, '
#  '{"__clsname__": "User", "firstname": "Bob", "lastname": "Blackthorn", "age": 31, "groups": []}, '
#  '{"__clsname__": "User", "firstname": "Carol", "lastname": "Corn", "age": 32, "groups": []}, '
#  '{"__clsname__": "User", "firstname": "Dave", "lastname": "Durian", "age": 33, "groups": []}, '
#  '{"__clsname__": "User", "firstname": "Eve", "lastname": "Elderberry", "age": 34, "groups": []}, '
#  '{"__clsname__": "User", "firstname": "Mallory", "lastname": "Melon", "age": 15, "groups": []}]')

# %% Hints
# - `dict()`
# - `dict.update()`
# - `object.__class__.__name__`

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> assert type(result)

>>> from pprint import pprint
>>> pprint(result, width=100)
('[{"__clsname__": "User", "firstname": "Alice", "lastname": "Apricot", "age": 30, "groups": []}, '
 '{"__clsname__": "User", "firstname": "Bob", "lastname": "Blackthorn", "age": 31, "groups": []}, '
 '{"__clsname__": "User", "firstname": "Carol", "lastname": "Corn", "age": 32, "groups": []}, '
 '{"__clsname__": "User", "firstname": "Dave", "lastname": "Durian", "age": 33, "groups": []}, '
 '{"__clsname__": "User", "firstname": "Eve", "lastname": "Elderberry", "age": 34, "groups": []}, '
 '{"__clsname__": "User", "firstname": "Mallory", "lastname": "Melon", "age": 15, "groups": []}]')
"""

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`

# %% Imports
import json

# %% Types
result: list[object]

# %% Data
class User:
    def __init__(self, firstname, lastname, age=None, groups=None):
        self.firstname = firstname
        self.lastname = lastname
        self.age = age
        self.groups = groups if groups else []

    def __repr__(self):
        clsname = self.__class__.__qualname__
        arguments = ', '.join(f'{k}={v!r}' for k,v in vars(self).items())
        return f'{clsname}({arguments})'


DATA = [
    User('Alice', 'Apricot', age=30),
    User('Bob', 'Blackthorn', age=31),
    User('Carol', 'Corn', age=32),
    User('Dave', 'Durian', age=33),
    User('Eve', 'Elderberry', age=34),
    User('Mallory', 'Melon', age=15),
]

# %% Result
result = ...

# %% About
# - Name: JSON Object Dataclass
# - Difficulty: easy
# - Lines: 5
# - Minutes: 5

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% English
# 1. Deserialize data from variable `DATA`
# 2. Use `json` module
# 3. Result write to variable `result`
# 4. Run doctests - all must succeed

# %% Polish
# 1. Zdeserializuj dane ze zmiennej `DATA`
# 2. Użyj modułu `json`
# 3. Wynik zapisz do zmiennej `result`
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Example
# >>> result
# [User(firstname='Alice', lastname='Apricot', age=30, groups=[]),
#  User(firstname='Bob', lastname='Blackthorn', age=31, groups=[]),
#  User(firstname='Carol', lastname='Corn', age=32, groups=[]),
#  User(firstname='Dave', lastname='Durian', age=33, groups=[]),
#  User(firstname='Eve', lastname='Elderberry', age=34, groups=[]),
#  User(firstname='Mallory', lastname='Melon', age=15, groups=[])]

# %% Hints
# - `dict.pop()`
# - `globals()`
# - `cls(**obj)`

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> type(result)
<class 'list'>

>>> len(result) > 0
True

>>> all(type(row) is User
...     for row in result)
True

>>> from pprint import pprint
>>> pprint(result)
[User(firstname='Alice', lastname='Apricot', age=30, groups=[]),
 User(firstname='Bob', lastname='Blackthorn', age=31, groups=[]),
 User(firstname='Carol', lastname='Corn', age=32, groups=[]),
 User(firstname='Dave', lastname='Durian', age=33, groups=[]),
 User(firstname='Eve', lastname='Elderberry', age=34, groups=[]),
 User(firstname='Mallory', lastname='Melon', age=15, groups=[])]
"""

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`

# %% Imports
import json
from dataclasses import dataclass

# %% Types

# %% Data
DATA = (
    '[{"__clsname__": "User", "firstname": "Alice", "lastname": "Apricot", "age": 30, "groups": []}, '
    '{"__clsname__": "User", "firstname": "Bob", "lastname": "Blackthorn", "age": 31, "groups": []}, '
    '{"__clsname__": "User", "firstname": "Carol", "lastname": "Corn", "age": 32, "groups": []}, '
    '{"__clsname__": "User", "firstname": "Dave", "lastname": "Durian", "age": 33, "groups": []}, '
    '{"__clsname__": "User", "firstname": "Eve", "lastname": "Elderberry", "age": 34, "groups": []}, '
    '{"__clsname__": "User", "firstname": "Mallory", "lastname": "Melon", "age": 15, "groups": []}]'
)


class User:
    def __init__(self, firstname, lastname, age=None, groups=None):
        self.firstname = firstname
        self.lastname = lastname
        self.age = age
        self.groups = groups if groups else []

    def __repr__(self):
        clsname = self.__class__.__qualname__
        arguments = ', '.join(f'{k}={v!r}' for k,v in vars(self).items())
        return f'{clsname}({arguments})'


# %% Result
result = ...