16.6. JSON Object

16.6.1. SetUp

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

16.6.2. Encoder

>>> class User:
...     def __init__(self, firstname, lastname, age):
...         self.firstname = firstname
...         self.lastname = lastname
...         self.age = age
...
...     def __repr__(self):
...         clsname = self.__class__.__name__
...         firstname = self.firstname
...         lastname = self.lastname
...         age = self.age
...         return f'{clsname}({firstname=}, {lastname=}, {age=})'
>>>
>>>
>>> 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),
... ]
>>>
>>>
>>> def encoder(obj):
...     clsname = type(obj).__name__
...     data = vars(obj)
...     return {'_clsname': clsname, **data}
>>>
>>>
>>> result = json.dumps(DATA, default=encoder, indent=2)
>>> print(result)
[
  {
    "_clsname": "User",
    "firstname": "Alice",
    "lastname": "Apricot",
    "age": 30
  },
  {
    "_clsname": "User",
    "firstname": "Bob",
    "lastname": "Blackthorn",
    "age": 31
  },
  {
    "_clsname": "User",
    "firstname": "Carol",
    "lastname": "Corn",
    "age": 32
  },
  {
    "_clsname": "User",
    "firstname": "Dave",
    "lastname": "Durian",
    "age": 33
  },
  {
    "_clsname": "User",
    "firstname": "Eve",
    "lastname": "Elderberry",
    "age": 34
  },
  {
    "_clsname": "User",
    "firstname": "Mallory",
    "lastname": "Melon",
    "age": 15
  }
]

16.6.3. Decoder

>>> DATA = """
... [
...   {
...     "_clsname": "User",
...     "firstname": "Alice",
...     "lastname": "Apricot",
...     "age": 30
...   },
...   {
...     "_clsname": "User",
...     "firstname": "Bob",
...     "lastname": "Blackthorn",
...     "age": 31
...   },
...   {
...     "_clsname": "User",
...     "firstname": "Carol",
...     "lastname": "Corn",
...     "age": 32
...   },
...   {
...     "_clsname": "User",
...     "firstname": "Dave",
...     "lastname": "Durian",
...     "age": 33
...   },
...   {
...     "_clsname": "User",
...     "firstname": "Eve",
...     "lastname": "Elderberry",
...     "age": 34
...   },
...   {
...     "_clsname": "User",
...     "firstname": "Mallory",
...     "lastname": "Melon",
...     "age": 15
...   }
... ]
... """
>>> class User:
...     def __init__(self, firstname, lastname, age):
...         self.firstname = firstname
...         self.lastname = lastname
...         self.age = age
...
...     def __repr__(self):
...         clsname = self.__class__.__name__
...         firstname = self.firstname
...         lastname = self.lastname
...         age = self.age
...         return f'{clsname}({firstname=}, {lastname=}, {age=})'
>>>
>>>
>>> def decoder(obj):
...     clsname = obj.pop('_clsname')
...     cls = globals()[clsname]
...     return cls(**obj)
>>>
>>> json.loads(DATA, object_hook=decoder)
[User(firstname='Alice', lastname='Apricot', age=30),
 User(firstname='Bob', lastname='Blackthorn', age=31),
 User(firstname='Carol', lastname='Corn', age=32),
 User(firstname='Dave', lastname='Durian', age=33),
 User(firstname='Eve', lastname='Elderberry', age=34),
 User(firstname='Mallory', lastname='Melon', age=15)]

16.6.4. Objects with Relations

>>> class User:
...     def __init__(self, firstname, lastname, email):
...         self.firstname = firstname
...         self.lastname = lastname
...         self.email = email
...
...     def __repr__(self):
...         clsname = self.__class__.__name__
...         firstname = self.firstname
...         lastname = self.lastname
...         age = self.age
...         return f'{clsname}({firstname=}, {lastname=}, {email=})'
>>>
>>>
>>> DATA = [
...     User('Alice', 'Apricot', email='alice@example.com'),
...     User('Bob', 'Blackthorn', email='bob@example.com'),
...     User('Carol', 'Corn', email='carol@example.com'),
...     User('Dave', 'Durian', email='dave@example.org'),
...     User('Eve', 'Elderberry', email='eve@example.org'),
...     User('Mallory', 'Melon', email='mallory@example.net'),
... ]
>>> class User:
...     def __init__(self, firstname, lastname, groups=None):
...         self.firstname = firstname
...         self.lastname = lastname
...         self.groups = groups if groups else []
...
...     def __repr__(self):
...         clsname = self.__class__.__name__
...         firstname = self.firstname
...         lastname = self.lastname
...         groups = self.groups
...         return f'{clsname}({firstname=}, {lastname=}, {groups=})'
>>>
>>> class Group:
...     def __init__(self, name):
...         self.name = name
...
...     def __repr__(self):
...         clsname = self.__class__.__name__
...         name = self.name
...         return f'{clsname}({name=})'
>>>
>>>
>>> DATA = [
...     User(firstname='Alice', lastname='Apricot', groups=[
...         Group('users'),
...         Group('staff'),
...     ]),
...
...     User(firstname='Bob', lastname='Blackthorn', groups=[
...         Group('users'),
...         Group('staff'),
...     ]),
...
...     User(firstname='Carol', lastname='Corn', groups=[
...         Group('users'),
...     ]),
...
...     User(firstname='Dave', lastname='Durian', groups=[
...         Group('users'),
...     ]),
...
...     User(firstname='Eve', lastname='Elderberry', groups=[
...         Group('users'),
...         Group('staff'),
...         Group('admins'),
...     ]),
...
...     User(firstname='Mallory', lastname='Melon', groups=[]),
... ]

Encoder:

>>> def encoder(obj):
...     clsname = type(obj).__name__
...     data = vars(obj)
...     return {'_clsname': clsname, **data}
>>>
>>>
>>> data = json.dumps(DATA, default=encoder, indent=2)
>>> print(data)
[
  {
    "_clsname": "User",
    "firstname": "Alice",
    "lastname": "Apricot",
    "groups": [
      {
        "_clsname": "Group",
        "name": "users"
      },
      {
        "_clsname": "Group",
        "name": "staff"
      }
    ]
  },
  {
    "_clsname": "User",
    "firstname": "Bob",
    "lastname": "Blackthorn",
    "groups": [
      {
        "_clsname": "Group",
        "name": "users"
      },
      {
        "_clsname": "Group",
        "name": "staff"
      }
    ]
  },
  {
    "_clsname": "User",
    "firstname": "Carol",
    "lastname": "Corn",
    "groups": [
      {
        "_clsname": "Group",
        "name": "users"
      }
    ]
  },
  {
    "_clsname": "User",
    "firstname": "Dave",
    "lastname": "Durian",
    "groups": [
      {
        "_clsname": "Group",
        "name": "users"
      }
    ]
  },
  {
    "_clsname": "User",
    "firstname": "Eve",
    "lastname": "Elderberry",
    "groups": [
      {
        "_clsname": "Group",
        "name": "users"
      },
      {
        "_clsname": "Group",
        "name": "staff"
      },
      {
        "_clsname": "Group",
        "name": "admins"
      }
    ]
  },
  {
    "_clsname": "User",
    "firstname": "Mallory",
    "lastname": "Melon",
    "groups": []
  }
]

Decoder:

>>> 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', groups=[Group(name='users'), Group(name='staff')]),
 User(firstname='Bob', lastname='Blackthorn', groups=[Group(name='users'), Group(name='staff')]),
 User(firstname='Carol', lastname='Corn', groups=[Group(name='users')]),
 User(firstname='Dave', lastname='Durian', groups=[Group(name='users')]),
 User(firstname='Eve', lastname='Elderberry', groups=[Group(name='users'), Group(name='staff'), Group(name='admins')]),
 User(firstname='Mallory', lastname='Melon', groups=[])]

16.6.5. 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 `DATA` to JSON
# 2. Use `json` module
# 3. Define variable `result` with the result
# 4. Run doctests - all must succeed

# %% Polish
# 1. Zserializuj dane `DATA` do JSON
# 2. Użyj modułu `json`
# 3. Zdefiniuj zmienną `result` z wynikiem
# 4. Uruchom doctesty - wszystkie muszą się powieść

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

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

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> assert type(result)

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

# %% 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
from typing import Callable
encoder: Callable[[object], dict]
result: str

# %% Data
class User:
    def __init__(self, firstname, lastname, age=None):
        self.firstname = firstname
        self.lastname = lastname
        self.age = age

    def __repr__(self):
        clsname = self.__class__.__name__
        firstname = self.firstname
        lastname = self.lastname
        age = self.age
        return f'{clsname}({firstname=}, {lastname=}, {age=})'


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
def encoder():
    ...


result = ...

# %% 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. Unserialize data from variable `DATA`
# 2. Use `json` module
# 3. Define variable `result` with the result
# 4. Run doctests - all must succeed

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

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

# %% Hints
# - `dict()`
# - `obj.__class__`
# - `cls.__name__`

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> assert type(result)

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

# %% 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
from typing import Callable
encoder: Callable[[object], dict]
result: str

# %% Data
class User:
    def __init__(self, firstname, lastname, age=None):
        self.firstname = firstname
        self.lastname = lastname
        self.age = age

    def __repr__(self):
        clsname = self.__class__.__name__
        firstname = self.firstname
        lastname = self.lastname
        age = self.age
        return f'{clsname}({firstname=}, {lastname=}, {age=})'


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

# %% Result
def decoder():
    ...


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. Serialize data `DATA` to JSON
# 2. To each row add `_clsname` with an object class name
# 3. Use `json` module
# 4. Define variable `result` with the result
# 5. Run doctests - all must succeed

# %% Polish
# 1. Zserializuj dane `DATA` do JSON
# 2. Do każdego wiersza dodaj `_clsname` z nazwą klasy obiektu
# 3. Użyj modułu `json`
# 4. Zdefiniuj zmienną `result` z wynikiem
# 5. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> result
# ('[{"_clsname": "User", "firstname": "Alice", "lastname": "Apricot", '
#  '"groups": [{"_clsname": "Group", "name": "users"}, {"_clsname": "Group", '
#  '"name": "staff"}]}, {"_clsname": "User", "firstname": "Bob", "lastname": '
#  '"Blackthorn", "groups": [{"_clsname": "Group", "name": "users"}, '
#  '{"_clsname": "Group", "name": "staff"}]}, {"_clsname": "User", "firstname": '
#  '"Carol", "lastname": "Corn", "groups": [{"_clsname": "Group", "name": '
#  '"users"}]}, {"_clsname": "User", "firstname": "Dave", "lastname": "Durian", '
#  '"groups": [{"_clsname": "Group", "name": "users"}]}, {"_clsname": "User", '
#  '"firstname": "Eve", "lastname": "Elderberry", "groups": [{"_clsname": '
#  '"Group", "name": "users"}, {"_clsname": "Group", "name": "staff"}, '
#  '{"_clsname": "Group", "name": "admins"}]}, {"_clsname": "User", '
#  '"firstname": "Mallory", "lastname": "Melon", "groups": []}]')

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

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> assert 'result' in globals(), \
'Variable `result` is not defined; assign result of your program to it.'

>>> assert result is not Ellipsis, \
'Variable `result` has an invalid value; assign result of your program to it.'

>>> assert type(result) is str, \
'Variable `result` has an invalid type; expected: `str`.'

>>> assert len(result) > 0, \
'Variable `result` has an invalid length; expected more than zero elements.'

>>> from pprint import pprint
>>> pprint(result, width=79)
('[{"_clsname": "User", "firstname": "Alice", "lastname": "Apricot", '
 '"groups": [{"_clsname": "Group", "name": "users"}, {"_clsname": "Group", '
 '"name": "staff"}]}, {"_clsname": "User", "firstname": "Bob", "lastname": '
 '"Blackthorn", "groups": [{"_clsname": "Group", "name": "users"}, '
 '{"_clsname": "Group", "name": "staff"}]}, {"_clsname": "User", "firstname": '
 '"Carol", "lastname": "Corn", "groups": [{"_clsname": "Group", "name": '
 '"users"}]}, {"_clsname": "User", "firstname": "Dave", "lastname": "Durian", '
 '"groups": [{"_clsname": "Group", "name": "users"}]}, {"_clsname": "User", '
 '"firstname": "Eve", "lastname": "Elderberry", "groups": [{"_clsname": '
 '"Group", "name": "users"}, {"_clsname": "Group", "name": "staff"}, '
 '{"_clsname": "Group", "name": "admins"}]}, {"_clsname": "User", '
 '"firstname": "Mallory", "lastname": "Melon", "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
from typing import Callable
encoder: Callable[[object], dict]
result: str

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

    def __repr__(self):
        clsname = self.__class__.__name__
        firstname = self.firstname
        lastname = self.lastname
        groups = self.groups
        return f'{clsname}({firstname=}, {lastname=}, {groups=})'


class Group:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        clsname = self.__class__.__name__
        name = self.name
        return f'{clsname}({name=})'


DATA = [
    User(firstname='Alice', lastname='Apricot', groups=[
        Group('users'),
        Group('staff'),
    ]),

    User(firstname='Bob', lastname='Blackthorn', groups=[
        Group('users'),
        Group('staff'),
    ]),

    User(firstname='Carol', lastname='Corn', groups=[
        Group('users'),
    ]),

    User(firstname='Dave', lastname='Durian', groups=[
        Group('users'),
    ]),

    User(firstname='Eve', lastname='Elderberry', groups=[
        Group('users'),
        Group('staff'),
        Group('admins'),
    ]),

    User(firstname='Mallory', lastname='Melon', groups=[]),
]

# %% Result
def encoder():
    ...

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. Define variable `result` with the result
# 4. Run doctests - all must succeed

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

# %% Expected
# >>> result
# [User(firstname='Alice', lastname='Apricot', groups=[Group(name='users'), Group(name='staff')]),
#  User(firstname='Bob', lastname='Blackthorn', groups=[Group(name='users'), Group(name='staff')]),
#  User(firstname='Carol', lastname='Corn', groups=[Group(name='users')]),
#  User(firstname='Dave', lastname='Durian', groups=[Group(name='users')]),
#  User(firstname='Eve', lastname='Elderberry', groups=[Group(name='users'), Group(name='staff'), Group(name='admins')]),
#  User(firstname='Mallory', lastname='Melon', groups=[])]

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

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> 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', groups=[Group(name='users'), Group(name='staff')]),
 User(firstname='Bob', lastname='Blackthorn', groups=[Group(name='users'), Group(name='staff')]),
 User(firstname='Carol', lastname='Corn', groups=[Group(name='users')]),
 User(firstname='Dave', lastname='Durian', groups=[Group(name='users')]),
 User(firstname='Eve', lastname='Elderberry', groups=[Group(name='users'), Group(name='staff'), Group(name='admins')]),
 User(firstname='Mallory', lastname='Melon', 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
from typing import Callable
decoder: Callable[[dict], object]
result: list[object]

# %% Data
DATA = (
    '[{"_clsname": "User", "firstname": "Alice", "lastname": "Apricot", '
    '"groups": [{"_clsname": "Group", "name": "users"}, {"_clsname": "Group", '
    '"name": "staff"}]}, {"_clsname": "User", "firstname": "Bob", "lastname": '
    '"Blackthorn", "groups": [{"_clsname": "Group", "name": "users"}, '
    '{"_clsname": "Group", "name": "staff"}]}, {"_clsname": "User", "firstname": '
    '"Carol", "lastname": "Corn", "groups": [{"_clsname": "Group", "name": '
    '"users"}]}, {"_clsname": "User", "firstname": "Dave", "lastname": "Durian", '
    '"groups": [{"_clsname": "Group", "name": "users"}]}, {"_clsname": "User", '
    '"firstname": "Eve", "lastname": "Elderberry", "groups": [{"_clsname": '
    '"Group", "name": "users"}, {"_clsname": "Group", "name": "staff"}, '
    '{"_clsname": "Group", "name": "admins"}]}, {"_clsname": "User", '
    '"firstname": "Mallory", "lastname": "Melon", "groups": []}]'
)


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

    def __repr__(self):
        clsname = self.__class__.__name__
        firstname = self.firstname
        lastname = self.lastname
        groups = self.groups
        return f'{clsname}({firstname=}, {lastname=}, {groups=})'


class Group:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        clsname = self.__class__.__name__
        name = self.name
        return f'{clsname}({name=})'


# %% Result
def decoder():
    ...

result = ...