3. Serializacja i deserializacja JSON

Format JSON jest podobny do zapisu dict w Python, ale różni się:

  • brak przecinka na końcu ostatniego elementu list
  • zawsze są stosowane podwójne cudzysłowia
  • true i false jest pisane małymi literami
  • zamiast None jest null

3.1. Zapis danych do formatu JSON

Code Listing 3.1. Zapis danych do formatu JSON
import json

DATA = {'first_name': 'Ivan', 'last_name': 'Ivanovic'}

json.dumps(DATA)
# '{"first_name": "Ivan", "last_name": "Ivanovic"}'

3.2. Odczyt danych z formatu JSON

Code Listing 3.2. Odczyt danych z formatu JSON
import json

DATA = '{"first_name": "Ivan", "last_name": "Ivanovic"}'

json.loads(DATA)
# {'first_name': 'Ivan', 'last_name': 'Ivanovic'}

3.3. Problemy z serializacją i deserializacją

  • Serializacja i deserializacja dat
  • Serializacja i deserializacja obiektów

3.4. Serializacja i pisanie własnych encoderów

>>> DATA = {'first_name': 'Ivan', 'last_name': 'Ivanovic'}

>>> import json
>>> json.dumps(DATA)
'{"first_name": "Ivan", "last_name": "Ivanovic"}'

Problem z rzutowaniem daty na JSON:

Code Listing 3.3. Exception during encoding datetime
import datetime
import json

DATA = {"datetime": datetime.datetime(1961, 4, 12, 2, 7, 0, 123456)}

json.dumps(DATA)
# Traceback (most recent call last):
#   ...
# TypeError: Object of type 'datetime' is not JSON serializable

3.4.1. Encoder Klasowy

Code Listing 3.4. Encoder Klasowy
import datetime
import json


DATA = {"datetime": datetime.datetime(1961, 4, 12, 2, 7, 0, 123456)}


class DatetimeEncoder(json.JSONEncoder):
    def default(self, obj):
        try:
            return super().default(obj)
        except TypeError:
            return f'{obj:%Y-%m-%dT%H:%M:%S.%fZ}'


json.dumps(DATA, cls=DatetimeEncoder)

3.4.2. Encoder Function

Code Listing 3.5. Encoder Function
import datetime
import json


DATA = {"datetime": datetime.datetime(1961, 4, 12, 2, 7, 0, 123456)}


def datetime_encoder(self, obj):
    if isinstance(obj, datetime.datetime):
        # return obj.isoformat()
        return f'{obj:%Y-%m-%dT%H:%M:%S.%fZ}'
    elif isinstance(obj, datetime.date):
        return f'{obj:%Y-%m-%d}'
    else:
        return None

json.JSONEncoder.default = datetime_encoder

out = json.dumps(DATA)
print(out)

3.4.3. Encoder Lambda

Code Listing 3.6. Encoder Lambda
import datetime
import json


DATA = {"datetime": datetime.datetime(1961, 4, 12, 2, 7, 0, 123456)}


json.JSONEncoder.default = lambda self, obj: (f'{obj:%Y-%m-%dT%H:%M:%S.%fZ}' if isinstance(obj, datetime.datetime) else None)

out = json.dumps(DATA)
print(out)
# '{"now": "1961-04-12T02:07:00.123456Z"}'

3.4.4. Encodowanie daty

Code Listing 3.7. Encoder dat do formatu JSON
import datetime

DATA = {
    "datetime": datetime.datetime(1961, 4, 12, 2, 7, 0, 123456),
    "astronaut": {
        "date": datetime.date(1923, 11, 18),
        "person": "jose.jimenez@nasa.gov"
    },
    "flight": [
        {"datetime": datetime.datetime(1961, 5, 5, 14, 34, 13), "action": "launch"},
        {"datetime": datetime.datetime(1961, 5, 5, 14, 49, 35), "action": "landing"}
    ]
}

3.4.5. Encodowanie obiektów

Code Listing 3.8. Encoder kalas do formatu JSON
import json


class Kontakt:
    def __init__(self, imie, nazwisko, adresy=[]):
        self.imie = imie
        self.nazwisko = nazwisko
        self.adresy = adresy

    def __str__(self):
        return f'{self.imie} {self.nazwisko} {self.adresy}'

    def __repr__(self):
        return self.__str__()


class Adres:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

    def __str__(self):
        return f'{self.__dict__}'

    def __repr__(self):
        return self.__str__()


ksiazka_adresowa = [
    Kontakt(imie='Max', nazwisko='Peck', adresy=[
        Adres(ulica='2101 E NASA Pkwy', miasto='Houston', stan='Texas', kod='77058', panstwo='USA'),
        Adres(ulica=None, miasto='Kennedy Space Center', kod='32899', panstwo='USA'),
        Adres(ulica='4800 Oak Grove Dr', miasto='Pasadena', kod='91109', panstwo='USA'),
        Adres(ulica='2825 E Ave P', miasto='Palmdale', stan='California', kod='93550', panstwo='USA'),
    ]),
    Kontakt(imie='José', nazwisko='Jiménez'),
    Kontakt(imie='Иван', nazwisko='Иванович', adresy=[]),
]


class KontaktEncoder(json.JSONEncoder):
    def default(self, obj):
        try:
            return super().default(obj)
        except TypeError:
            return obj.__dict__

out = json.dumps(ksiazka_adresowa, cls=KontaktEncoder)
print(out)

3.5. Deserializacja i pisanie własnych decoderów

Code Listing 3.9. Daty w formacie JSON domyślnie nie są parsowane
import json

DATA = '{"now":"1969-07-21T02:41:15.000"}'

json.loads(DATA)
# {'now': '1969-07-21T02:41:15.000'}

3.5.1. Dekodowanie daty

3.5.2. Dekodowanie klasą

Code Listing 3.10. Decoder dat do formatu JSON
import datetime
import json

DATA = """
{
    "astronaut":{
        "date": "1923-11-18",
        "person": "jose.jimenez@nasa.gov"
    },
    "flight": [
        {"datetime": "1961-05-05T14:34:13.000Z", "action": "launch"},
        {"datetime": "1961-05-05T14:49:35.000Z", "action": "landing"}
    ]
}
"""

class DatetimeDecoder(json.JSONDecoder):
    def __init__(self):
        json.JSONDecoder.__init__(self, object_hook=self.convert_datetime)

    def convert_datetime(slef, args):
        for key, value in args.items():

            if key == 'datetime':
                dt = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
                args['datetime'] = dt.replace(tzinfo=datetime.timezone.utc)
            elif key == 'date':
                dt = datetime.datetime.strptime(value, '%Y-%m-%d')
                args['date'] = dt.date()

        return args


out = json.loads(DATA, cls=DatetimeDecoder)
print(out)

3.5.3. Function decoder

Code Listing 3.11. Decoder dat do formatu JSON
import datetime
import json


DATA = """
{
    "astronaut":{
        "datetime":"1961-05-05T14:34:13.640Z",
        "person":"jose.jimenez@nasa.gov"
    },
    "first-flight":[
        {"datetime":"1961-05-05T14:34:13.640Z", "action":"launch"},
        {"datetime":"1961-05-05T14:49:35.640Z", "action":"landing"},
    ],
}
"""

def datetime_decoder(obj):
    for key, value in obj.items():
        if key == 'datetime':
            obj['datetime'] = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ').replace(tzinfo=datetime.timezone.utc)
    return obj


out = json.loads(DATA, object_hook=datetime_decoder)
print(out)

3.5.4. Dekodowanie obiektów

3.6. Zadania kontrolne

3.6.1. Serializacja dat

  1. Skopiuj do swojego pliku strukturę danych listing-json-encoder-data
  2. Zapisz ją do pliku json
  3. Wczyraj ją z pliku json jako obiekty Pythonowe (ten sam efekt co na listingu)
Co zadanie sprawdz:
 
  • Serializacja danych
  • Korzystanie z biblioteki JSON
  • Serializowanie zagnieżdżonych obiektów

3.6.2. Serializacja obiektów do JSON

  1. Użyj obiektu książka_adresowa stworzonego w zadaniu z programowaniem obiektowym
  2. Zapisz kontakty z książki adresowej w JSON
  3. Jak odtworzyć relacje?
  4. Stwórz obiekty książki adresowej na podstawie danych odczytanych z pliku
Podpowiedź:self.__dict__
Code Listing 3.12. Odczyt danych z formatu JSON
import json

DATA = '{"first_name": "Ivan", "last_name": "Ivanovic"}'

json.loads(DATA)
# {'first_name': 'Ivan', 'last_name': 'Ivanovic'}