8.6. JSON Object¶
8.6.1. SetUp¶
>>> from pprint import pprint
>>> from dataclasses import dataclass
>>> import json
8.6.2. Encode Object¶
>>> @dataclass
... class User:
... firstname: str
... lastname: str
>>>
>>> DATA = User('Mark', 'Watney')
>>>
>>> data = vars(DATA)
>>> result = json.dumps(data)
>>>
>>> print(result)
{"firstname": "Mark", "lastname": "Watney"}
8.6.3. Decode Object¶
>>> @dataclass
... class User:
... firstname: str
... lastname: str
>>>
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney"
... }"""
>>>
>>> data = json.loads(DATA)
>>> result = User(**data)
>>>
>>> print(result)
User(firstname='Mark', lastname='Watney')
8.6.4. Object Encoder¶
>>> @dataclass
... class User:
... firstname: str
... lastname: str
>>>
>>> DATA = User('Mark', 'Watney')
>>>
>>>
>>> def encoder(obj):
... return vars(obj)
>>>
>>>
>>> result = json.dumps(DATA, default=encoder)
>>> print(result)
{"firstname": "Mark", "lastname": "Watney"}
8.6.5. Object Decoder¶
>>> @dataclass
... class User:
... firstname: str
... lastname: str
>>>
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney"
... }"""
>>>
>>>
>>> def decoder(data):
... return User(**data)
>>>
>>>
>>> result = json.loads(DATA, object_hook=decoder)
>>> print(result)
User(firstname='Mark', lastname='Watney')
8.6.6. Encode Object with Relation¶
>>> @dataclass
... class Group:
... gid: int
... name: str
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: list[Group]
>>>
>>>
>>> DATA = [
... User('Mark', 'Watney', groups=[
... Group(gid=1, name='users')]),
... User('Melissa', 'Lewis', groups=[
... Group(gid=1, name='users'),
... Group(gid=2, name='admins')]),
... User('Rick', 'Martinez', 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": "Mark",
"lastname": "Watney",
"groups": [
{
"_clsname": "Group",
"gid": 1,
"name": "users"
}
]
},
{
"_clsname": "User",
"firstname": "Melissa",
"lastname": "Lewis",
"groups": [
{
"_clsname": "Group",
"gid": 1,
"name": "users"
},
{
"_clsname": "Group",
"gid": 2,
"name": "admins"
}
]
},
{
"_clsname": "User",
"firstname": "Rick",
"lastname": "Martinez",
"groups": []
}
]
8.6.7. Decode¶
Encoding nested objects with relations to JSON:
>>> @dataclass
... class Group:
... gid: int
... name: str
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... role: str
... groups: list[Group]
>>>
>>>
>>> DATA = (
... '[{"_clsname":"User","firstname":"Mark","lastname":"Watney","rol'
... 'e":"user","groups":[{"_clsname":"Group","gid":1,"name":"users"}'
... ']},{"_clsname":"User","firstname":"Melissa","lastname":"Lewis",'
... '"role":"admin","groups":[{"_clsname":"Group","gid":1,"name":"us'
... 'ers"},{"_clsname":"Group","gid":2,"name":"admins"}]},{"_clsname'
... '":"User","firstname":"Rick","lastname":"Martinez","role":"guest'
... '","groups":[]}]'
... )
>>>
>>>
>>> def decoder(obj):
... clsname = obj.pop('_clsname')
... cls = globals()[clsname]
... return cls(**obj)
>>>
>>>
>>> result = json.loads(DATA, object_hook=decoder)
>>> pprint(result, width=72)
[User(firstname='Mark',
lastname='Watney',
role='user',
groups=[Group(gid=1, name='users')]),
User(firstname='Melissa',
lastname='Lewis',
role='admin',
groups=[Group(gid=1, name='users'), Group(gid=2, name='admins')]),
User(firstname='Rick', lastname='Martinez', role='guest', groups=[])]
8.6.8. Use Case - 0x01¶
>>> import json
>>> from dataclasses import dataclass, field
>>> from pprint import pprint
>>>
>>>
>>> @dataclass
... class Group:
... gid: int
... name: str
>>>
>>> @dataclass
... class User:
... lastname: str
... firstname: str
... groups: list[Group]
>>>
>>>
>>> DATA = [
... User('Mark', 'Watney', groups=[
... Group(gid=1, name='users')]),
... User('Melissa', 'Lewis', groups=[
... Group(gid=1, name='users'),
... Group(gid=2, name='admins')]),
... User('Rick', 'Martinez', 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)
('[{"lastname": "Mark", "firstname": "Watney", "groups": [{"gid": 1, '
'"name": "users", "_clsname": "Group"}], "_clsname": "User"}, '
'{"lastname": "Melissa", "firstname": "Lewis", "groups": [{"gid": 1, '
'"name": "users", "_clsname": "Group"}, {"gid": 2, "name": "admins", '
'"_clsname": "Group"}], "_clsname": "User"}, {"lastname": "Rick", '
'"firstname": "Martinez", "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, width=72)
[User(lastname='Mark',
firstname='Watney',
groups=[Group(gid=1, name='users')]),
User(lastname='Melissa',
firstname='Lewis',
groups=[Group(gid=1, name='users'), Group(gid=2, name='admins')]),
User(lastname='Rick', firstname='Martinez', groups=[])]
8.6.9. Assignments¶
"""
* Assignment: JSON Object Factory
* Complexity: medium
* Lines of code: 5 lines
* Time: 5 min
English:
1. Convert from JSON format to Python using decoder function
2. Create instances of `Setosa`, `Virginica`, `Versicolor`
classes based on value in field "species"
3. Add instances to `result: list[Setosa|Virginica|Versicolor]`
4. Run doctests - all must succeed
Polish:
1. Przekonwertuj dane z JSON do Python używając dekodera funkcyjnego
2. Twórz obiekty klas `Setosa`, `Virginica`, `Versicolor`
w zależności od wartości pola "species"
3. Dodawaj instancje do `result: list[Setosa|Virginica|Versicolor]`
4. Uruchom doctesty - wszystkie muszą się powieść
Hint:
* `dict.pop()`
* `globals()`
* Assignment Expression
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> assert type(result)
>>> assert len(result) == 9
>>> classes = (Setosa, Virginica, Versicolor)
>>> assert all(type(row) in classes for row in result)
>>> result[0]
Virginica(sepalLength=5.8, sepalWidth=2.7, petalLength=5.1, petalWidth=1.9)
>>> result[1]
Setosa(sepalLength=5.1, sepalWidth=3.5, petalLength=1.4, petalWidth=0.2)
"""
import json
from dataclasses import dataclass
FILE = r'_temporary.json'
DATA = (
'[{"sepalLength":5.8,"sepalWidth":2.7,"petalLength":5.1,"petalWidth":1.9,'
'"species":"virginica"},{"sepalLength":5.1,"sepalWidth":3.5,"petalLength"'
':1.4,"petalWidth":0.2,"species":"setosa"},{"sepalLength":5.7,"sepalWidth'
'":2.8,"petalLength":4.1,"petalWidth":1.3,"species":"versicolor"},{"sepal'
'Length":6.3,"sepalWidth":2.9,"petalLength":5.6,"petalWidth":1.8,"species'
'":"virginica"},{"sepalLength":6.4,"sepalWidth":3.2,"petalLength":4.5,"pe'
'talWidth":1.5,"species":"versicolor"},{"sepalLength":4.7,"sepalWidth":3.'
'2,"petalLength":1.3,"petalWidth":0.2,"species":"setosa"},{"sepalLength":'
'7.0,"sepalWidth":3.2,"petalLength":4.7,"petalWidth":1.4,"species":"versi'
'color"},{"sepalLength":7.6,"sepalWidth":3.0,"petalLength":6.6,"petalWidt'
'h":2.1,"species":"virginica"},{"sepalLength":4.9,"sepalWidth":3.0,"petal'
'Length":1.4,"petalWidth":0.2,"species":"setosa"}]')
@dataclass
class Iris:
sepalLength: float
sepalWidth: float
petalLength: float
petalWidth: float
class Setosa(Iris):
pass
class Virginica(Iris):
pass
class Versicolor(Iris):
pass
# JSON decoded DATA
result = ...
"""
* Assignment: JSON Object Dataclass
* Complexity: easy
* Lines of code: 15 lines
* Time: 13 min
English:
1. `DATA` is a JSON downloaded from https://api.github.com/users
3. Using `dataclass` model data as class `User`
4. Iterate over records and create instances of this class
5. Collect all instances to one list
6. Run doctests - all must succeed
Polish:
1. `DATA` to JSON pobrany z https://api.github.com/users
3. Używając `dataclass` zamodeluj dane za pomocą klasy `User`
4. Iterując po rekordach twórz instancje tej klasy
5. Zbierz wszystkie instancje do jednej listy
6. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> type(result)
<class 'list'>
>>> len(result) > 0
True
>>> all(type(row) is User
... for row in result)
True
>>> result[0] # doctest: +NORMALIZE_WHITESPACE
User(login='myuser',
id=1,
node_id='MDQ6VXNlcjE=',
avatar_url='https://avatars.githubusercontent.com/u/1?v=4',
gravatar_id='',
url='https://api.github.com/users/myuser',
html_url='https://github.com/myuser',
followers_url='https://api.github.com/users/myuser/followers',
following_url='https://api.github.com/users/myuser/following',
gists_url='https://api.github.com/users/myuser/gists{/gist_id}',
starred_url='https://api.github.com/users/myuser/starred',
subscriptions_url='https://api.github.com/users/myuser/subscriptions',
organizations_url='https://api.github.com/users/myuser/orgs',
repos_url='https://api.github.com/users/myuser/repos',
events_url='https://api.github.com/users/myuser/events{/privacy}',
received_events_url='https://api.github.com/users/myuser',
type='User',
site_admin=False)
"""
import json
from dataclasses import dataclass
DATA = (
'[{"login":"myuser","id":1,"node_id":"MDQ6VXNlcjE=","avatar_url":"http'
's://avatars.githubusercontent.com/u/1?v=4","gravatar_id":"","url":"ht'
'tps://api.github.com/users/myuser","html_url":"https://github.com/myu'
'ser","followers_url":"https://api.github.com/users/myuser/followers",'
'"following_url":"https://api.github.com/users/myuser/following","gist'
's_url":"https://api.github.com/users/myuser/gists{/gist_id}","starred'
'_url":"https://api.github.com/users/myuser/starred","subscriptions_ur'
'l":"https://api.github.com/users/myuser/subscriptions","organizations'
'_url":"https://api.github.com/users/myuser/orgs","repos_url":"https:/'
'/api.github.com/users/myuser/repos","events_url":"https://api.github.'
'com/users/myuser/events{/privacy}","received_events_url":"https://api'
'.github.com/users/myuser","type":"User","site_admin":false},{"login":'
'"defunkt","id":2,"node_id":"MDQ6VXNlcjI=","avatar_url":"https://avata'
'rs.githubusercontent.com/u/2?v=4","gravatar_id":"","url":"https://api'
'.github.com/users/defunkt","html_url":"https://github.com/defunkt","f'
'ollowers_url":"https://api.github.com/users/defunkt/followers","follo'
'wing_url":"https://api.github.com/users/defunkt/following","gists_url'
'":"https://api.github.com/users/defunkt/gists{/gist_id}","starred_url'
'":"https://api.github.com/users/defunkt/starred","subscriptions_url":'
'"https://api.github.com/users/defunkt/subscriptions","organizations_u'
'rl":"https://api.github.com/users/defunkt/orgs","repos_url":"https://'
'api.github.com/users/defunkt/repos","events_url":"https://api.github.'
'com/users/defunkt/events{/privacy}","received_events_url":"https://ap'
'i.github.com/users/defunkt","type":"User","site_admin":false},{"login'
'":"pjhyett","id":3,"node_id":"MDQ6VXNlcjM=","avatar_url":"https://ava'
'tars.githubusercontent.com/u/3?v=4","gravatar_id":"","url":"https://a'
'pi.github.com/users/pjhyett","html_url":"https://github.com/pjhyett",'
'"followers_url":"https://api.github.com/users/pjhyett/followers","fol'
'lowing_url":"https://api.github.com/users/pjhyett/following","gists_u'
'rl":"https://api.github.com/users/pjhyett/gists{/gist_id}","starred_u'
'rl":"https://api.github.com/users/pjhyett/starred","subscriptions_url'
'":"https://api.github.com/users/pjhyett/subscriptions","organizations'
'_url":"https://api.github.com/users/pjhyett/orgs","repos_url":"https:'
'//api.github.com/users/pjhyett/repos","events_url":"https://api.githu'
'b.com/users/pjhyett/events{/privacy}","received_events_url":"https://'
'api.github.com/users/pjhyett","type":"User","site_admin":false}]')
@dataclass
class User:
pass
# JSON decoded DATA
result = ...