15.5. JSON Decoder — Python
15.5.1. Problem
Problem with
date,datetime,time,timedeltaPython does not decode values automatically
15.5.2. SetUp
>>> from datetime import date >>> import json
15.5.3. Problem
>>> DATA = """{ ... "firstname": "Alice", ... "lastname": "Apricot", ... "birthdate": "2000-01-01" ... }""" >>> >>> json.loads(DATA) {'firstname': 'Alice', 'lastname': 'Apricot', 'birthdate': '2000-01-01'}
15.5.4. Solution - Function Based Decoder
This works for simple (flat) data
This works for nested data structures
>>> DATA = """{ ... "firstname": "Alice", ... "lastname": "Apricot", ... "birthdate": "2000-01-01" ... }""" >>> >>> >>> def decoder(obj): ... for key, value in obj.items(): ... if key == 'birthdate': ... obj[key] = date.fromisoformat(value) ... return obj >>> >>> >>> json.loads(DATA, object_hook=decoder) {'firstname': 'Alice', 'lastname': 'Apricot', 'birthdate': datetime.date(2000, 1, 1)}
15.5.5. Solution - Class Based Decoder
>>> DATA = """{ ... "firstname": "Alice", ... "lastname": "Apricot", ... "birthdate": "2000-01-01" ... }""" >>> >>> >>> class Decoder(json.JSONDecoder): ... def default(self, obj): ... for key, value in obj.items(): ... if key == 'birthdate': ... obj[key] = date.fromisoformat(value) ... return obj ... ... def __init__(self): ... super().__init__(object_hook=self.default) >>> >>> >>> json.loads(DATA, cls=Decoder) {'firstname': 'Alice', 'lastname': 'Apricot', 'birthdate': datetime.date(2000, 1, 1)}
15.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": "Alice", ... "lastname": "Apricot", ... "birthdate": "2000-01-01", ... "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': 'Alice', 'lastname': 'Apricot', 'birthdate': datetime.date(2000, 1, 1), '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)}
15.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": "Alice", ... "lastname": "Apricot", ... "birthdate": "2000-01-01", ... "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': 'Alice', 'lastname': 'Apricot', 'birthdate': datetime.date(2000, 1, 1), '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)}
15.5.8. Use Case - 3
>>> from datetime import datetime, date, time, timedelta >>> import json >>> from pprint import pprint >>> >>> >>> DATA = """{ ... "firstname": "Alice", ... "lastname": "Apricot", ... "birthdate": "2000-01-01", ... "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': 'Alice', 'lastname': 'Apricot', 'birthdate': datetime.date(2000, 1, 1), '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)}
15.5.9. Use Case - 4
>>> from datetime import datetime, date, time, timedelta >>> import json >>> from pprint import pprint >>> >>> >>> DATA = """{ ... "firstname": "Alice", ... "lastname": "Apricot", ... "birthdate": "2000-01-01", ... "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': 'Alice', 'lastname': 'Apricot', 'birthdate': datetime.date(2000, 1, 1), '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)}
15.5.10. Use Case - 5
>>> from datetime import datetime, date, time, timedelta >>> import json >>> >>> >>> DATA = """{ ... "firstname": "Alice", ... "lastname": "Apricot", ... "birthdate": "2000-01-01", ... "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': 'Alice', 'lastname': 'Apricot', 'birthdate': datetime.date(2000, 1, 1), '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)}
15.5.11. Assignments
# %% About # - Name: JSON Decoder Function # - Difficulty: easy # - Lines: 6 # - Minutes: 3 # %% 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ść # %% Expected # >>> result # [{'firstname': 'Alice', 'lastname': 'Apricot', 'lastlogin': datetime.date(2000, 1, 1)}, # {'firstname': 'Bob', 'lastname': 'Blackthorn', '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 has an is invalid version; expected: `3.9` or newer.' >>> from inspect import isfunction >>> assert isfunction(decoder), \ 'Decoder must be a function' >>> assert type(result) is list, \ 'Variable `result` has an invalid type; expected: `list`.' >>> assert len(result) > 0, \ 'Variable `result` has an invalid length; expected more than zero elements.' >>> from pprint import pprint >>> pprint(result, width=120, sort_dicts=False) [{'firstname': 'Alice', 'lastname': 'Apricot', 'lastlogin': datetime.date(2000, 1, 1)}, {'firstname': 'Bob', 'lastname': 'Blackthorn', '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 -f -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": "Blackthorn", "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 def decoder(): ... 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ść # %% Expected # >>> result # [{'firstname': 'Alice', 'lastname': 'Apricot', 'lastlogin': datetime.date(2000, 1, 1)}, # {'firstname': 'Bob', 'lastname': 'Blackthorn', '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 has an is invalid version; expected: `3.9` or newer.' >>> 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, \ 'Variable `result` has an invalid type; expected: `list`.' >>> assert len(result) > 0, \ 'Variable `result` has an invalid length; expected more than zero elements.' >>> from pprint import pprint >>> pprint(result, width=120, sort_dicts=False) [{'firstname': 'Alice', 'lastname': 'Apricot', 'lastlogin': datetime.date(2000, 1, 1)}, {'firstname': 'Bob', 'lastname': 'Blackthorn', '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 -f -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": "Blackthorn", "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 class Decoder: ... result = ...