16.5. JSON Decoder
16.5.1. Problem
Problem with
date
,datetime
,time
,timedelta
Python does not decode values automatically
16.5.2. SetUp
>>> from pprint import pprint
>>> from datetime import date
>>> import json
16.5.3. Problem
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney",
... "birthdate": "1994-10-12"
... }"""
>>>
>>> result = json.loads(DATA)
>>> print(result)
{'firstname': 'Mark',
'lastname': 'Watney',
'birthdate': '1994-10-12'}
16.5.4. Function Decoder
This works for simple (flat) data
This works for nested data structures
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney",
... "birthdate": "1994-10-12"
... }"""
>>>
>>>
>>> def decoder(data: dict) -> dict:
... for field, value in data.items():
... if field == 'birthdate':
... data[field] = date.fromisoformat(value)
... return data
>>>
>>>
>>> result = json.loads(DATA, object_hook=decoder)
>>> print(result)
{'firstname': 'Mark',
'lastname': 'Watney',
'birthdate': datetime.date(1994, 10, 12)}
16.5.5. Context Dependency Injection
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney",
... "birthdate": "1994-10-12"
... }"""
>>>
>>>
>>> class Decoder(json.JSONDecoder):
... def __init__(self):
... super().__init__(object_hook=self.default)
...
... def default(self, data: dict) -> dict:
... for field, value in data.items():
... if field == 'birthdate':
... data[field] = date.fromisoformat(value)
... return data
>>>
>>>
>>> result = json.loads(DATA, cls=Decoder)
>>> print(result)
{'firstname': 'Mark',
'lastname': 'Watney',
'birthdate': datetime.date(1994, 10, 12)}
16.5.6. Use Case - 1
This works for simple (flat) data
This won't work for nested data structures
>>> import json
>>> from datetime import datetime, date, time, timedelta
>>> from pprint import pprint
>>>
>>>
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney",
... "birthdate": "1994-10-12",
... "launch": "1969-07-21T02:56:15",
... "landing": "12:30:00",
... "flight_time": 15552000,
... "duration": 13
... }"""
>>>
>>>
>>> def decoder(obj: dict) -> dict:
... obj['birthdate'] = date.fromisoformat(obj['birthdate'])
... obj['launch'] = datetime.fromisoformat(obj['launch'])
... obj['landing'] = time.fromisoformat(obj['landing'])
... obj['flight_time'] = timedelta(seconds=obj['flight_time'])
... obj['duration'] = timedelta(days=obj['duration'])
... return obj
>>>
>>>
>>> result = json.loads(DATA, object_hook=decoder)
>>> pprint(result, sort_dicts=False)
{'firstname': 'Mark',
'lastname': 'Watney',
'birthdate': datetime.date(1994, 10, 12),
'launch': datetime.datetime(1969, 7, 21, 2, 56, 15),
'landing': datetime.time(12, 30),
'flight_time': datetime.timedelta(days=180),
'duration': datetime.timedelta(days=13)}
16.5.7. Use Case - 2
This works for simple (flat) data
This won't work for nested data structures
>>> import json
>>> from datetime import datetime, date, time, timedelta
>>> from pprint import pprint
>>>
>>>
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney",
... "birthdate": "1994-10-12",
... "launch": "1969-07-21T02:56:15",
... "landing": "12:30:00",
... "flight_time": 15552000,
... "duration": 13
... }"""
>>>
>>>
>>> def decoder(obj: dict) -> dict:
... return obj | {
... 'birthdate': date.fromisoformat(obj['birthdate']),
... 'launch': datetime.fromisoformat(obj['launch']),
... 'landing': time.fromisoformat(obj['landing']),
... 'flight_time': timedelta(seconds=obj['flight_time']),
... 'duration': timedelta(days=obj['duration']),
... }
>>>
>>>
>>> result = json.loads(DATA, object_hook=decoder)
>>> pprint(result, sort_dicts=False)
{'firstname': 'Mark',
'lastname': 'Watney',
'birthdate': datetime.date(1994, 10, 12),
'launch': datetime.datetime(1969, 7, 21, 2, 56, 15),
'landing': datetime.time(12, 30),
'flight_time': datetime.timedelta(days=180),
'duration': datetime.timedelta(days=13)}
16.5.8. Use Case - 3
>>> from datetime import datetime, date, time, timedelta
>>> import json
>>> from pprint import pprint
>>>
>>>
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney",
... "birthdate": "1994-10-12",
... "launch": "1969-07-21T02:56:15",
... "landing": "12:30:00",
... "flight_time": 15552000,
... "duration": 13
... }"""
>>>
>>>
>>> def decoder(x):
... for key, value in x.items():
... match key:
... case 'birthdate':
... x[key] = date.fromisoformat(value)
... case 'launch':
... x[key] = datetime.fromisoformat(value)
... case 'landing':
... x[key] = time.fromisoformat(value)
... case 'flight_time' | 'mission_time':
... x[key] = timedelta(seconds=float(value))
... case 'duration':
... x[key] = timedelta(days=int(value))
... return x
>>>
>>>
>>> result = json.loads(DATA, object_hook=decoder)
>>> pprint(result, sort_dicts=False)
{'firstname': 'Mark',
'lastname': 'Watney',
'birthdate': datetime.date(1994, 10, 12),
'launch': datetime.datetime(1969, 7, 21, 2, 56, 15),
'landing': datetime.time(12, 30),
'flight_time': datetime.timedelta(days=180),
'duration': datetime.timedelta(days=13)}
16.5.9. Use Case - 4
>>> from datetime import datetime, date, time, timedelta
>>> import json
>>> from pprint import pprint
>>>
>>>
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney",
... "birthdate": "1994-10-12",
... "launch": "1969-07-21T02:56:15",
... "landing": "12:30:00",
... "flight_time": 15552000,
... "duration": 13
... }"""
>>>
>>>
>>> class Decoder(json.JSONDecoder):
... def __init__(self) -> None:
... super().__init__(object_hook=lambda data: {
... field: getattr(self, field)(value)
... for field, value in data.items()})
...
... def firstname(self, value: str) -> str:
... return value
...
... def lastname(self, value: str) -> str:
... return value
...
... def birthdate(self, value: str) -> date:
... return date.fromisoformat(value)
...
... def launch(self, value: str) -> datetime:
... return datetime.fromisoformat(value)
...
... def landing(self, value: str) -> time:
... return time.fromisoformat(value)
...
... def flight_time(self, value: str) -> timedelta:
... return timedelta(seconds=float(value))
...
... def duration(self, value: str) -> timedelta:
... return timedelta(days=int(value))
>>>
>>>
>>> result = json.loads(DATA, cls=Decoder)
>>> pprint(result, sort_dicts=False)
{'firstname': 'Mark',
'lastname': 'Watney',
'birthdate': datetime.date(1994, 10, 12),
'launch': datetime.datetime(1969, 7, 21, 2, 56, 15),
'landing': datetime.time(12, 30),
'flight_time': datetime.timedelta(days=180),
'duration': datetime.timedelta(days=13)}
16.5.10. Use Case - 5
>>> from datetime import datetime, date, time, timedelta
>>> import json
>>>
>>>
>>> DATA = """{
... "firstname": "Mark",
... "lastname": "Watney",
... "birthdate": "1994-10-12",
... "launch": "1969-07-21T02:56:15",
... "landing": "12:30:00",
... "flight_time": 15552000,
... "duration": 13
... }"""
>>>
>>>
>>> class Decoder(json.JSONDecoder):
... def __init__(self):
... super().__init__(object_hook=lambda data: {
... field: self.default(field, value)
... for field, value in data.items()})
...
... def default(self, field, value):
... result = {
... 'birthdate': lambda x: date.fromisoformat(x),
... 'launch': lambda x: datetime.fromisoformat(x),
... 'landing': lambda x: time.fromisoformat(x),
... 'duration': lambda x: timedelta(days=x),
... 'flight_time': lambda x: timedelta(seconds=x),
... }.get(field, value)
... return result(value) if callable(result) else result
>>>
>>>
>>> result = json.loads(DATA, cls=Decoder)
>>> print(result)
{'firstname': 'Mark',
'lastname': 'Watney',
'birthdate': datetime.date(1994, 10, 12),
'launch': datetime.datetime(1969, 7, 21, 2, 56, 15),
'landing': datetime.time(12, 30),
'flight_time': datetime.timedelta(days=180),
'duration': datetime.timedelta(days=13)}
16.5.11. Assignments
# %% About
# - Name: JSON Decoder Function
# - Difficulty: easy
# - Lines: 6
# - 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 and encoder function
# 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` i enkodera funkcji
# 3. Wynik zapisz do zmiennej `result`
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Example
# >>> result
# [{'firstname': 'Alice', 'lastname': 'Apricot', 'lastlogin': datetime.date(2000, 1, 1)},
# {'firstname': 'Bob', 'lastname': 'Banana', 'lastlogin': datetime.date(2000, 1, 2)},
# {'firstname': 'Carol', 'lastname': 'Corn', 'lastlogin': datetime.date(2000, 1, 3)},
# {'firstname': 'Dave', 'lastname': 'Durian', 'lastlogin': datetime.date(2000, 1, 4)},
# {'firstname': 'Eve', 'lastname': 'Elderberry', 'lastlogin': datetime.date(2000, 1, 5)},
# {'firstname': 'Mallory', 'lastname': 'Melon', 'lastlogin': None}]
# %% Hints
# - `json.loads(object_hook=...)`
# - `dict.items()`
# - `datetime.fromisoformat()`
# - `date.fromisoformat()`
# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction
>>> assert isfunction(decoder), \
'Decoder must be a function'
>>> assert type(result) is list, \
'Result must be a list'
>>> assert len(result) > 0, \
'Result cannot be empty'
>>> from pprint import pprint
>>> pprint(result, width=120, sort_dicts=False)
[{'firstname': 'Alice', 'lastname': 'Apricot', 'lastlogin': datetime.date(2000, 1, 1)},
{'firstname': 'Bob', 'lastname': 'Banana', 'lastlogin': datetime.date(2000, 1, 2)},
{'firstname': 'Carol', 'lastname': 'Corn', 'lastlogin': datetime.date(2000, 1, 3)},
{'firstname': 'Dave', 'lastname': 'Durian', 'lastlogin': datetime.date(2000, 1, 4)},
{'firstname': 'Eve', 'lastname': 'Elderberry', 'lastlogin': datetime.date(2000, 1, 5)},
{'firstname': 'Mallory', 'lastname': 'Melon', 'lastlogin': None}]
"""
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% Imports
import json
from datetime import date
# %% Types
result: dict[str, str|date]
# %% Data
DATA = """[
{"firstname": "Alice", "lastname": "Apricot", "lastlogin": "2000-01-01"},
{"firstname": "Bob", "lastname": "Banana", "lastlogin": "2000-01-02"},
{"firstname": "Carol", "lastname": "Corn", "lastlogin": "2000-01-03"},
{"firstname": "Dave", "lastname": "Durian", "lastlogin": "2000-01-04"},
{"firstname": "Eve", "lastname": "Elderberry", "lastlogin": "2000-01-05"},
{"firstname": "Mallory", "lastname": "Melon", "lastlogin": null}
]"""
# %% Result
result = ...
# %% About
# - Name: JSON Decoder Class
# - Difficulty: easy
# - Lines: 9
# - 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 and encoder class
# 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` i enkodera klasy
# 3. Wynik zapisz do zmiennej `result`
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Example
# >>> result
# [{'firstname': 'Alice', 'lastname': 'Apricot', 'lastlogin': datetime.date(2000, 1, 1)},
# {'firstname': 'Bob', 'lastname': 'Banana', 'lastlogin': datetime.date(2000, 1, 2)},
# {'firstname': 'Carol', 'lastname': 'Corn', 'lastlogin': datetime.date(2000, 1, 3)},
# {'firstname': 'Dave', 'lastname': 'Durian', 'lastlogin': datetime.date(2000, 1, 4)},
# {'firstname': 'Eve', 'lastname': 'Elderberry', 'lastlogin': datetime.date(2000, 1, 5)},
# {'firstname': 'Mallory', 'lastname': 'Melon', 'lastlogin': None}]
# %% Hints
# - `json.loads(object_hook=...)`
# - `dict.items()`
# - `datetime.fromisoformat()`
# - `date.fromisoformat()`
# - `super().__init__(object_hook=...)`
# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isclass
>>> assert isclass(Decoder), \
'Decoder must be a class'
>>> assert issubclass(Decoder, json.JSONDecoder), \
'Decoder must inherit from `json.JSONDecoder`'
>>> assert type(result) is list, \
'Result must be a list'
>>> assert len(result) > 0, \
'Result cannot be empty'
>>> from pprint import pprint
>>> pprint(result, width=120, sort_dicts=False)
[{'firstname': 'Alice', 'lastname': 'Apricot', 'lastlogin': datetime.date(2000, 1, 1)},
{'firstname': 'Bob', 'lastname': 'Banana', 'lastlogin': datetime.date(2000, 1, 2)},
{'firstname': 'Carol', 'lastname': 'Corn', 'lastlogin': datetime.date(2000, 1, 3)},
{'firstname': 'Dave', 'lastname': 'Durian', 'lastlogin': datetime.date(2000, 1, 4)},
{'firstname': 'Eve', 'lastname': 'Elderberry', 'lastlogin': datetime.date(2000, 1, 5)},
{'firstname': 'Mallory', 'lastname': 'Melon', 'lastlogin': None}]
"""
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% Imports
import json
from datetime import date
# %% Types
result: dict[str, str|date]
# %% Data
DATA = """[
{"firstname": "Alice", "lastname": "Apricot", "lastlogin": "2000-01-01"},
{"firstname": "Bob", "lastname": "Banana", "lastlogin": "2000-01-02"},
{"firstname": "Carol", "lastname": "Corn", "lastlogin": "2000-01-03"},
{"firstname": "Dave", "lastname": "Durian", "lastlogin": "2000-01-04"},
{"firstname": "Eve", "lastname": "Elderberry", "lastlogin": "2000-01-05"},
{"firstname": "Mallory", "lastname": "Melon", "lastlogin": null}
]"""
# %% Result
result = ...