Рубрики
Без рубрики

json – Нотация объектов JavaScript

Автор оригинала: Doug Hellmann.

Цель:

Кодировать объекты Python как строки JSON и декодировать JSON строки в объекты Python.

Модуль json предоставляет API, аналогичный pickle, для преобразования объектов Python в памяти в сериализованное представление, известное как нотация объектов JavaScript (JSON). В отличие от pickle, JSON имеет то преимущество, что он реализован на многих языках (особенно JavaScript). Он наиболее широко используется для связи между веб-сервером и клиентом в REST API, но также полезен для других нужд взаимодействия между приложениями.

Кодирование и декодирование простых типов данных

Кодировщик понимает собственные типы Python по умолчанию ( str , int , float , list , tuple и dict ).

json_simple_types.py

import json

data  [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

data_string  json.dumps(data)
print('JSON:', data_string)

Значения кодируются способом, внешне похожим на вывод Python repr () .

$ python3 json_simple_types.py

DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
JSON: [{"a": "A", "b": [2, 4], "c": 3.0}]

Кодирование с последующим повторным декодированием может не дать точно такой же тип объекта.

json_simple_types_decode.py

import json

data  [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA   :', data)

data_string  json.dumps(data)
print('ENCODED:', data_string)

decoded  json.loads(data_string)
print('DECODED:', decoded)

print('ORIGINAL:', type(data[0]['b']))
print('DECODED :', type(decoded[0]['b']))

В частности, кортежи становятся списками.

$ python3 json_simple_types_decode.py

DATA   : [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
ENCODED: [{"a": "A", "b": [2, 4], "c": 3.0}]
DECODED: [{'a': 'A', 'b': [2, 4], 'c': 3.0}]
ORIGINAL: 
DECODED : 

Расходные материалы для людей и компактный вывод

Еще одно преимущество JSON перед pickle заключается в том, что результаты читаются человеком. Функция dumps () принимает несколько аргументов, чтобы сделать вывод еще лучше. Например, флаг sort_keys указывает кодировщику выводить ключи словаря в отсортированном, а не в случайном порядке.

json_sort_keys.py

import json

data  [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

unsorted  json.dumps(data)
print('JSON:', json.dumps(data))
print('SORT:', json.dumps(data, sort_keysTrue))

first  json.dumps(data, sort_keysTrue)
second  json.dumps(data, sort_keysTrue)

print('UNSORTED MATCH:', unsorted  first)
print('SORTED MATCH  :', first  second)

Сортировка упрощает просмотр результатов на глаз, а также дает возможность сравнивать вывод JSON в тестах.

$ python3 json_sort_keys.py

DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
JSON: [{"a": "A", "b": [2, 4], "c": 3.0}]
SORT: [{"a": "A", "b": [2, 4], "c": 3.0}]
UNSORTED MATCH: True
SORTED MATCH  : True

Для структур данных с высокой степенью вложенности укажите значение для indent , чтобы выходные данные также были правильно отформатированы.

json_indent.py

import json

data  [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

print('NORMAL:', json.dumps(data, sort_keysTrue))
print('INDENT:', json.dumps(data, sort_keysTrue, indent2))

Когда indent является неотрицательным целым числом, вывод более похож на вывод pprint, с ведущими пробелами для каждого уровня структуры данных, соответствующими уровню отступа.

$ python3 json_indent.py

DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
NORMAL: [{"a": "A", "b": [2, 4], "c": 3.0}]
INDENT: [
  {
    "a": "A",
    "b": [
      2,
      4
    ],
    "c": 3.0
  }
]

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

json_compact_encoding.py

import json

data  [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

print('repr(data)             :', len(repr(data)))

plain_dump  json.dumps(data)
print('dumps(data)            :', len(plain_dump))

small_indent  json.dumps(data, indent2)
print('dumps(data,, len(small_indent))

with_separators  json.dumps(data, separators(',', ':'))
print('dumps(data, separators):', len(with_separators))

Аргумент separators для dumps () должен быть кортежем, содержащим строки для разделения элементов в списке и ключей от значений в словаре. По умолчанию используется (',', ':') . Удалив пробелы, получается более компактный вывод.

$ python3 json_compact_encoding.py

DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
repr(data)             : 35
dumps(data)            : 35
dumps(data,
dumps(data, separators): 29

Словари кодирования

Формат JSON предполагает, что ключи словаря будут строками. Попытка кодировать словарь с нестроковыми типами в качестве ключей приводит к ошибке TypeError . Один из способов обойти это ограничение – указать кодировщику пропускать нестроковые ключи с помощью аргумента skipkeys :

json_skipkeys.py

import json

data  [{'a': 'A', 'b': (2, 4), 'c': 3.0, ('d',): 'D tuple'}]

print('First attempt')
try:
    print(json.dumps(data))
except TypeError as err:
    print('ERROR:', err)

print()
print('Second attempt')
print(json.dumps(data, skipkeysTrue))

Вместо того, чтобы вызывать исключение, нестроковый ключ игнорируется.

$ python3 json_skipkeys.py

First attempt
ERROR: keys must be str, int, float, bool or None, not tuple

Second attempt
[{"a": "A", "b": [2, 4], "c": 3.0}]

Работа с пользовательскими типами

До сих пор во всех примерах использовались встроенные типы Pythons, поскольку они изначально поддерживаются json . Также часто требуется кодировать пользовательские классы, и есть два способа сделать это.

Учитывая этот класс для кодирования:

json_myobj.py

class MyObj:

    def __init__(self, s):
        self.s  s

    def __repr__(self):
        return ''.format(self.s)

Простой способ кодирования экземпляра MyObj – определить функцию для преобразования неизвестного типа в известный тип. Кодирование не требуется, поэтому нужно просто преобразовать один объект в другой.

json_dump_default.py

import json
import json_myobj

obj  json_myobj.MyObj('instance value goes here')

print('First attempt')
try:
    print(json.dumps(obj))
except TypeError as err:
    print('ERROR:', err)


def convert_to_builtin_type(obj):
    print('default(', repr(obj), ')')
    # Convert objects to a dictionary of their representation
    d  {
        '__class__': obj.__class__.__name__,
        '__module__': obj.__module__,
    }
    d.update(obj.__dict__)
    return d


print()
print('With default')
print(json.dumps(obj, defaultconvert_to_builtin_type))

В convert_to_builtin_type () экземпляры классов, не распознаваемых json , преобразуются в словари с достаточной информацией для воссоздания объекта, если программа имеет доступ к необходимым модулям Python.

$ python3 json_dump_default.py

First attempt
ERROR: Object of type MyObj is not JSON serializable

With default
default(  )
{"__class__": "MyObj", "__module__": "json_myobj", "s": "instance
value goes here"}

Чтобы декодировать результаты и создать экземпляр MyObj () , используйте аргумент object_hook для load () , чтобы привязать его к декодеру, чтобы класс можно импортировать из модуля и использовать для создания экземпляра.

object_hook вызывается для каждого словаря, декодированного из входящего потока данных, что дает возможность преобразовать словарь в другой тип объекта. Функция-ловушка должна возвращать объект, который вызывающее приложение должно получить вместо словаря.

json_load_object_hook.py

import json


def dict_to_object(d):
    if '__class__' in d:
        class_name  d.pop('__class__')
        module_name  d.pop('__module__')
        module  __import__(module_name)
        print('MODULE:', module.__name__)
        class_  getattr(module, class_name)
        print('CLASS:', class_)
        args  {
            key: value
            for key, value in d.items()
        }
        print('INSTANCE ARGS:', args)
        inst  class_(**args)
    else:
        inst  d
    return inst


encoded_object  '''
    [{"s": "instance value goes here",
      "__module__": "json_myobj", "__class__": "MyObj"}]
    '''

myobj_instance  json.loads(
    encoded_object,
    object_hookdict_to_object,
)
print(myobj_instance)

Поскольку json преобразует строковые значения в объекты Unicode, их необходимо повторно закодировать как строки ASCII, прежде чем их можно будет использовать в качестве аргументов ключевого слова для конструктора класса.

$ python3 json_load_object_hook.py

MODULE: json_myobj
CLASS: 
INSTANCE ARGS: {'s': 'instance value goes here'}
[]

Подобные ловушки доступны для встроенных типов целых чисел ( parse_int ), чисел с плавающей запятой ( parse_float ) и констант ( parse_constant ).

Классы кодировщика и декодера

Помимо уже рассмотренных вспомогательных функций, модуль json предоставляет классы для кодирования и декодирования. Использование классов напрямую дает доступ к дополнительным API-интерфейсам для настройки их поведения.

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

json_encoder_iterable.py

import json

encoder  json.JSONEncoder()
data  [{'a': 'A', 'b': (2, 4), 'c': 3.0}]

for part in encoder.iterencode(data):
    print('PART:', part)

Вывод формируется в логических единицах, а не на основе какого-либо значения размера.

$ python3 json_encoder_iterable.py

PART: [
PART: {
PART: "a"
PART: :
PART: "A"
PART: ,
PART: "b"
PART: :
PART: [2
PART: , 4
PART: ]
PART: ,
PART: "c"
PART: :
PART: 3.0
PART: }
PART: ]

Метод encode () в основном эквивалентен '' .join (encoder.iterencode ()) с некоторой дополнительной проверкой ошибок заранее.

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

json_encoder_default.py

import json
import json_myobj


class MyEncoder(json.JSONEncoder):

    def default(self, obj):
        print('default(', repr(obj), ')')
        # Convert objects to a dictionary of their representation
        d  {
            '__class__': obj.__class__.__name__,
            '__module__': obj.__module__,
        }
        d.update(obj.__dict__)
        return d


obj  json_myobj.MyObj('internal data')
print(obj)
print(MyEncoder().encode(obj))

Результат такой же, как и в предыдущей реализации.

$ python3 json_encoder_default.py


default(  )
{"__class__": "MyObj", "__module__": "json_myobj", "s": "internal
data"}

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

json_decoder_object_hook.py

import json


class MyDecoder(json.JSONDecoder):

    def __init__(self):
        json.JSONDecoder.__init__(
            self,
            object_hookself.dict_to_object,
        )

    def dict_to_object(self, d):
        if '__class__' in d:
            class_name  d.pop('__class__')
            module_name  d.pop('__module__')
            module  __import__(module_name)
            print('MODULE:', module.__name__)
            class_  getattr(module, class_name)
            print('CLASS:', class_)
            args  {
                key: value
                for key, value in d.items()
            }
            print('INSTANCE ARGS:', args)
            inst  class_(**args)
        else:
            inst  d
        return inst


encoded_object  '''
[{"s": "instance value goes here",
  "__module__": "json_myobj", "__class__": "MyObj"}]
'''

myobj_instance  MyDecoder().decode(encoded_object)
print(myobj_instance)

Результат такой же, как в предыдущем примере.

$ python3 json_decoder_object_hook.py

MODULE: json_myobj
CLASS: 
INSTANCE ARGS: {'s': 'instance value goes here'}
[]

Работа с потоками и файлами

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

json_dump_file.py

import io
import json

data  [{'a': 'A', 'b': (2, 4), 'c': 3.0}]

f  io.StringIO()
json.dump(data, f)

print(f.getvalue())

Сокет или обычный дескриптор файла будут работать так же, как и буфер StringIO , используемый в этом примере.

$ python3 json_dump_file.py

[{"a": "A", "b": [2, 4], "c": 3.0}]

Хотя функция load () не оптимизирована для одновременного чтения только части данных, она по-прежнему предлагает преимущество инкапсуляции логики создания объектов из потокового ввода.

json_load_file.py

import io
import json

f  io.StringIO('[{"a": "A", "c": 3.0, "b": [2, 4]}]')
print(json.load(f))

Как и в случае с dump () , в load () можно передать любой файловый объект.

$ python3 json_load_file.py

[{'a': 'A', 'c': 3.0, 'b': [2, 4]}]

Смешанные потоки данных

JSONDecoder включает raw_decode () , метод декодирования структуры данных, за которой следуют дополнительные данные, например данные JSON с завершающим текстом. Возвращаемое значение – это объект, созданный путем декодирования входных данных, и индекс этих данных, указывающий, где декодирование остановилось.

json_mixed_data.py

import json

decoder  json.JSONDecoder()


def get_decoded_and_remainder(input_data):
    obj, end  decoder.raw_decode(input_data)
    remaining  input_data[end:]
    return (obj, end, remaining)


encoded_object  '[{"a": "A", "c": 3.0, "b": [2, 4]}]'
extra_text  'This text is not JSON.'

print('JSON first:')
data  ' '.join([encoded_object, extra_text])
obj, end, remaining  get_decoded_and_remainder(data)

print('Object              :', obj)
print('End of parsed input :', end)
print('Remaining text      :', repr(remaining))

print()
print('JSON embedded:')
try:
    data  ' '.join([extra_text, encoded_object, extra_text])
    obj, end, remaining  get_decoded_and_remainder(data)
except ValueError as err:
    print('ERROR:', err)

К сожалению, это работает только в том случае, если объект появляется в начале ввода.

$ python3 json_mixed_data.py

JSON first:
Object              : [{'a': 'A', 'c': 3.0, 'b': [2, 4]}]
End of parsed input : 35
Remaining text      : ' This text is not JSON.'

JSON embedded:
ERROR: Expecting value: line 1 column 1 (char 0)

JSON в командной строке

Модуль json.tool реализует программу командной строки для переформатирования данных JSON для облегчения чтения.

[{"a": "A", "c": 3.0, "b": [2, 4]}]

Входной файл example.json содержит сопоставление с ключами, не расположенными в алфавитном порядке. В первом примере ниже показаны данные, переформатированные по порядку, а во втором примере используется --sort-keys для сортировки ключей сопоставления перед печатью вывода.

$ python3 -m json.tool example.json

[
    {
        "a": "A",
        "c": 3.0,
        "b": [
            2,
            4
        ]
    }
]

$ python3 -m json.tool --sort-keys example.json

[
    {
        "a": "A",
        "b": [
            2,
            4
        ],
        "c": 3.0
    }
]

Смотрите также