6.4. JSON Encoder

6.4.1. Rationale

  • Problem with date, datetime, time, timedelta

  • Exception during encoding datetime

  • Encoder will be used, when standard procedure fails

>>> from datetime import date
>>> import json
>>>
>>>
>>> DATA = {'firstname': 'Mark',
...         'lastname': 'Watney',
...         'born': date(1994, 10, 12)}
>>>
>>> result = json.dumps(DATA)
Traceback (most recent call last):
TypeError: Object of type date is not JSON serializable

6.4.2. Monkey Patching Lambda Expression

>>> from datetime import date
>>> import json
>>>
>>>
>>> DATA = {'firstname': 'Mark',
...         'lastname': 'Watney',
...         'born': date(1994, 10, 12)}
>>>
>>> json.JSONEncoder.default = lambda self,x: x.isoformat()
>>> result = json.dumps(DATA)
>>>
>>> print(result)
{"firstname": "Mark", "lastname": "Watney", "born": "1994-10-12"}

6.4.3. Monkey Patching Function

>>> from datetime import date
>>> import json
>>>
>>>
>>> DATA = {'firstname': 'Mark',
...         'lastname': 'Watney',
...         'born': date(1994, 10, 12)}
>>>
>>>
>>> def encoder(self, x):
...     return x.isoformat()
>>>
>>> json.JSONEncoder.default = encoder
>>> result = json.dumps(DATA)
>>>
>>> print(result)
{"firstname": "Mark", "lastname": "Watney", "born": "1994-10-12"}

6.4.4. Content Dependency Injection

>>> from datetime import date
>>> import json
>>>
>>>
>>> DATA = {'firstname': 'Mark',
...         'lastname': 'Watney',
...         'born': date(1994, 10, 12)}
>>>
>>>
>>> class MyEncoder(json.JSONEncoder):
...     def default(self, x):
...         return x.strftime('%Y-%m-%d')
>>>
>>>
>>> result = json.dumps(DATA, cls=MyEncoder)
>>>
>>> print(result)  
{"firstname": "Mark",
 "lastname": "Watney",
 "born": "1994-10-12"}

6.4.5. Use Case

>>> from datetime import date, time, datetime, timedelta
>>> import json
>>>
>>>
>>> DATA = {'name': 'Mark Watney',
...         'born': date(1994, 10, 12),
...         'launch': datetime(1969, 7, 21, 2, 56, 15),
...         'landing': time(12, 30),
...         'duration': timedelta(days=13)}
>>>
>>>
>>> class MyEncoder(json.JSONEncoder):
...     def default(self, value):
...         if type(value) in (datetime, date, time):
...             return value.isoformat()
...         if type(value) is timedelta:
...             return value.days
>>>
>>>
>>> result = json.dumps(DATA, cls=MyEncoder)
>>>
>>> print(result)  
{"name": "Mark Watney",
 "born": "1994-10-12",
 "launch": "1969-07-21T02:56:15",
 "landing": "12:30:00",
 "duration": 13}

6.4.6. Assignments

Code 6.6. Solution
"""
* Assignment: JSON Encoder Martian
* Complexity: medium
* Lines of code: 4 lines
* Time: 8 min

English:
    1. Define `result: str` with JSON encoded `DATA`
    2. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `result: str` z zakodowanym `DATA` w JSON
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass

    >>> assert result is not Ellipsis, \
    'Assign result to variable: `result`'

    >>> assert type(result) is str, \
    'Variable `result` has invalid type, should be str'

    >>> assert isclass(Encoder), \
    'Encoder must be a class'

    >>> assert issubclass(Encoder, json.JSONEncoder), \
    'Encoder must inherit from `json.JSONEncoder`'

    >>> print(result)  # doctest: +NORMALIZE_WHITESPACE
    {"mission": "Ares 3",
     "launch_date": "2035-06-29T00:00:00",
     "destination": "Mars",
     "destination_landing": "2035-11-07T00:00:00",
     "destination_location": "Acidalia Planitia",
     "crew": [{"name": "Melissa Lewis", "born": "1995-07-15"},
              {"name": "Rick Martinez", "born": "1996-01-21"},
              {"name": "Alex Vogel", "born": "1994-11-15"},
              {"name": "Chris Beck", "born": "1999-08-02"},
              {"name": "Beth Johansen", "born": "2006-05-09"},
              {"name": "Mark Watney", "born": "1994-10-12"}]}
"""

import json
from datetime import date, datetime


DATA = {'mission': 'Ares 3',
        'launch_date': datetime(2035, 6, 29),
        'destination': 'Mars',
        'destination_landing': datetime(2035, 11, 7),
        'destination_location': 'Acidalia Planitia',
        'crew': [
            {'name': 'Melissa Lewis', 'born': date(1995, 7, 15)},
            {'name': 'Rick Martinez', 'born': date(1996, 1, 21)},
            {'name': 'Alex Vogel', 'born': date(1994, 11, 15)},
            {'name': 'Chris Beck', 'born': date(1999, 8, 2)},
            {'name': 'Beth Johansen', 'born': date(2006, 5, 9)},
            {'name': 'Mark Watney', 'born': date(1994, 10, 12)}]}


class Encoder:
    ...


# str: JSON encoded DATA
result = ...