JSON

Сериализация - представление данных в удобном для пересылки по сети виде.

Разработка схемы сохранения сохранения данных.

Версионность. Обратная и прямая совместимость.

Почему не стоит использовать pickle.

Источники

JSON терминолгия

JSON - JavaScript Object Notation. Обычно используется для обмена данными между сервером и веб-приложениями.

Как выглядит типичные данные на json? Очень похоже на словарь (dict) в python.

{
    "firstName": "Jane",
    "lastName": "Doe",
    "hobbies": ["running", "sky diving", "singing"],
    "age": 35,
    "children": [
        {
            "firstName": "Alice",
            "age": 6
        },
        {
            "firstName": "Bob",
            "age": 8
        }
    ]
}

Таблица соотвествия встроенных типов данных в Python и json.

Encoder, наследованный от json.JSONEncoder

Python JSON
dict object
list (tuple) array
str string
int, float number
True true
False false
None null
класс JSON

import json

Пакет json входит в стандартную поставку.

Разбор JSON данных (parse json)

Разбор строки json.loads (s - значит строка)

Обычно json представляют в виде строки. Строка превращается в словарь функцией loads(str):

import json

person = '{"name": "Bob", "languages": ["English", "Fench"]}'
person_dict = json.loads(person)

# Output: {'name': 'Bob', 'languages': ['English', 'Fench']}
print( person_dict)

# Output: ['English', 'French']
print(person_dict['languages'])

Чтение из файла json.load

Сохраним данные в формате json в файле person.json:

{"name": "Bob", 
"languages": ["English", "Fench"]
}

Прочитаем этот файл и конвертируем данные в словарь:

import json

with open('person.json') as f:
  data = json.load(f)

# Output: {'name': 'Bob', 'languages': ['English', 'Fench']}
print(data)

Конвертация в json

Конвертация словаря в строку json.dumps (s - значит строка)

import json

person_dict = {'name': 'Bob',
'age': 12,
'children': None
}
person_json = json.dumps(person_dict)

# Output: {"name": "Bob", "age": 12, "children": null}
print(person_json)

Конвертация словаря в файл json.dump


import json

person_dict = {
    "name": "Bob",
    "languages": ["English", "Fench"],
    "married": True,
    "age": 32
}

with open('person.txt', 'w') as json_file:
  json.dump(person_dict, json_file)

Получим файл person.txt:

{"name": "Bob", "languages": ["English", "Fench"], "married": true, "age": 32}

№# Свой формат разделителей

Если не устраивают разделители , и : между полями, то можно задать свой набор разделителей в параметре separators=(". ", " = ").

Значения по умолчанию separators=(", ", ": ")

import json

x = {
  "name": "John",
  "age": 30,
  "married": True,
  "divorced": False,
  "children": ("Ann","Billy"),
  "pets": None,
  "cars": [
    {"model": "BMW 230", "mpg": 27.5},
    {"model": "Ford Edge", "mpg": 24.1}
  ]
}

# use . and a space to separate objects, and a space, a = and a space to separate keys from their values:
print(json.dumps(x, indent=4, separators=(". ", " = ")))

Получим

{
    "name" = "John".
    "age" = 30.
    "married" = true.
    "divorced" = false.
    "children" = [
        "Ann".
        "Billy"
    ],
    "pets" = null.
    "cars" = [
        {
            "model" = "BMW 230".
            "mpg" = 27.5
        }.
        {
            "model" = "Ford Edge".
            "mpg" = 24.1
        }
    ]
}

Типы данных

Заметим, что load и loads возвращает словарь с ключами-строками. Если ранее ключи были числами, то эта информация потеряна.

loads(dumps(x)) != x   # если х был ключом НЕ строкой

pretty print (удобная печать) через indent

Для анализа (и поиска ошибок) удобно печатать json в human-readable виде.

import json

person_string = '{"name": "Bob", "languages": "English", "numbers": [2, 1.6, null]}'

# Getting dictionary
person_dict = json.loads(person_string)

# Pretty Printing JSON string back
print(json.dumps(person_dict, indent = 4, sort_keys=True))
  • indent - отступы в виде количества пробелов (None по умолчанию);
  • sort_keys - надо ли сортировать ключи (False по умолчанию).

получим:

{
    "languages": "English",
    "name": "Bob",
    "numbers": [
        2,
        1.6,
        null
    ]
}

Сериализация несериализуемых объектов (классов)

__dict__ или vars()

Можно из несериализуемого объекта ob делаеть сериализуемый словарь, используя __dict__ явно или как результат вызова функции vars().

import json

class Person(object):
    def __init__(self):
        self.name = 'John'
        self.age = 25
        self.id = 1

person = Person()

#save to file
dt = {}
dt.update(vars(person))
print(dt, type(dt))
with open("/home/test/person.txt", "w") as file:
    json.dump(dt, file)

В файле /home/test/person.txt получим

{"id": 1, "age": 25, "name": "John"}

Если у нас есть вложенные объекты, то лучше передать функцию, которая из объекта делает словарь:

# https://blog.softhints.com/python-convert-object-to-json-3-examples/
import json

class error:
    def __init__(self):
        self.errorCode="server issue"
        self.errorMessage="201"


class BaseRespone():
    def __init__(self):
        self.success = "data fetch successfully"
        self.data={"succss":"msg"}
        self.error1 = error()

def obj_to_dict(obj):
   return obj.__dict__


bs = BaseRespone()
# json_string = json.dumps(bs.__dict__,  default = obj_to_dict)
json_string = json.dumps(bs,  default = obj_to_dict)
print('json_string=', json_string, type(json_string))

json_string1 = json.loads(json_string)
print('json_string1=', json_string1, type(json_string1))

получим:

('json_string=', '{"error1": {"errorCode": "server issue", "errorMessage": "201"}, "data": {"succss": "msg"}, "success": "data fetch successfully"}', <type 'str'>)
('json_string1=', {u'error1': {u'errorCode': u'server issue', u'errorMessage': u'201'}, u'data': {u'succss': u'msg'}, u'success': u'data fetch successfully'}, <type 'dict'>)

Или, как мы уже видели ранее, используем встроенную функцию vars():

json_string = json.dumps(bs,  default = vars)

Упрощение представлия объекта

Возьмем комплексное число. Оно не сериализуется.

>>> z = 3 + 8j
>>> type(z)
<class 'complex'>
>>> json.dumps(z)
TypeError: Object of type 'complex' is not JSON serializable

Попробуем упростить его представление и восстановить объект из упрощеного представления, чтобы полученный объект был равен исходному.

>>> z.real
3.0
>>> z.imag
8.0
>>> complex(3, 8) == z
True

То есть для представления комплексного числа достаточно значений z.real и z.imag.

Сериализуем и десериализуем объект, пользуясь этим упрощенным представлением.

Encoding Custom Type via default parameter

Передадим в метод dump параметр default. Питон будет вызывать переданную функцию каждый раз, когда объект не будет сериализоваться естественным образом (как строки, списки или числа и тп.)

class Elf:
    pass
def encode_complex(z):
    if isinstance(z, complex):
        return (z.real, z.imag)
    else:
        type_name = z.__class__.__name__
        raise TypeError(f"Object of type '{type_name}' is not JSON serializable")

>>> json.dumps(9 + 5j, default=encode_complex)
'[9.0, 5.0]'
>>> json.dumps(elf, default=encode_complex)
TypeError: Object of type 'Elf' is not JSON serializable

Можно закодировать комплесное число как tuple, можно сделать из него словарь (как мы делали раньше).

Encoding Custom Type via JSONEncoder subclass

Напишем класс, который наследуется от класса JSONEncoder и переопределяет метод default(self, z).

class ComplexEncoder(json.JSONEncoder):
    def default(self, z):
        if isinstance(z, complex):          # комплексное число? упрощаем!
            return (z.real, z.imag)
        else:                               # остальные объекты - обычное поведение
            super().default(self, z)

вызываем json.dumps() или ComplexEncoder().encode():

>>> json.dumps(2 + 5j, cls=ComplexEncoder)
'[2.0, 5.0]'

>>> encoder = ComplexEncoder()
>>> encoder.encode(3 + 6j)
'[3.0, 6.0]'

Decoding Custom Types

Проблема: если мы декодируем полученную строку '[3.0, 6.0]', то получим список чисел, а не комплексное число.

>>> complex_json = json.dumps(4 + 17j, cls=ComplexEncoder)
>>> json.loads(complex_json)
[4.0, 17.0]

Нужно определить какие данные необходимы и достаточны для восстановления прежнего объекта.

Допустим, у нас есть такой файл complex_data.json:

{
    "__complex__": true,
    "real": 42,
    "imag": 36
}

Восстановим из него данные с помощью функции:

def decode_complex(dct):
    if "__complex__" in dct:
        return complex(dct["real"], dct["imag"])
    return dct

Восстановление:

>>> with open("complex_data.json") as complex_data:
...     data = complex_data.read()
...     z = json.loads(data, object_hook=decode_complex)
... 
>>> type(z)
<class 'complex'>

Даже если объект состоит из набора таких данных, то этот подход продолжет работать. Пусть в файле лежит список комплексных чисел:

[
  {
    "__complex__":true,
    "real":42,
    "imag":36
  },
  {
    "__complex__":true,
    "real":64,
    "imag":11
  }
]

Восстановим их как раньше:

>>> with open("complex_data.json") as complex_data:
...     data = complex_data.read()
...     numbers = json.loads(data, object_hook=decode_complex)
... 
>>> numbers
[(42+36j), (64+11j)]

Задача

Дописать в проект игры save и load.

Можно в каждом классе реализовать функцию to_json и статический метод from_json.

results matching ""

    No results matching ""