Статьи

JSON-R: расширение JSON, имеющее дело с объектными ссылками (циркулярными и другими)

Ссылка JSON:

JSON , известный формат обмена данными, основанный на JavaScript, плохо справляется с множественными ссылками на одни и те же экземпляры объекта и даже внезапно терпит неудачу с циклическими ссылками. Чтобы проиллюстрировать эти две проблемы, возьмем следующий пример:
var usa = { name: "USA", language: "English", currency: "Dollar" };
var cities = [
	{ name: "Washington DC", country: usa },
	{ name: "New York", country: usa },
	{ name: "San Francisco", country: usa },
];
alert(JSON.stringify(cities, null, '\t'));

Что вы получите, если запустите приведенный выше код:

[
	{
		"name": "Washington DC",
		"country": {
			"name": "USA",
			"language": "English",
			"currency": "Dollar"
		}
	},
	{
		"name": "New York",
		"country": {
			"name": "USA",
			"language": "English",
			"currency": "Dollar"
		}
	},
	{
		"name": "San Francisco",
		"country": {
			"name": "USA",
			"language": "English",
			"currency": "Dollar"
		}
	}
]

Несмотря на то, что этот результат сериализации является правильным, он явно избыточен и неэффективен по сравнению с сериализацией, которая приведёт в соответствие только один раз для экземпляра США для Вашингтона, округ Колумбия, и даст краткую ссылку на этот экземпляр для Нью-Йорка и Сан-Франциско.

Добавление циклической ссылки в модель обязательно приведет к сбою JSON:

var usa = { name: "USA", language: "English", currency: "Dollar" };
var cities = [
	{ name: "Washington DC", country: usa },
	{ name: "New York", country: usa },
	{ name: "San Francisco", country: usa },
];

usa.capital = cities[0]; // ie. Washington DC (circular reference)

alert(JSON.stringify(cities, null, '\t'));

С Chrome вы получите эту ошибку в консоли JavaScript:

Uncaught TypeError: Converting circular structure to JSON

Несколько человек пытались преодолеть это ограничение. Я, конечно, знаю только о очень немногих из них, таких как » cycle.js  » (от Дугласа Крокфорда, создателя формата JSON) и » dojox.json.ref  » (от Криса Ципа).

Однако эти решения требуют добавления определенных свойств или объектов для ссылок и часто глубоко копируют объекты JavaScript, что приводит к проблемам с производительностью. Вместо того, чтобы вставлять короткий ключ или индекс для ссылки, они иногда используют путь к графу (например, «# path.to.array.2.and.so.on»), который может быть как минимум многословным при работе с глубокими графами , 

Демонстрация JSON-R, Quick!

Все источники Json-R являются общественным достоянием и могут быть загружены по следующему адресуhttps://github.com/graniteds/jsonr .

Чтобы быстро увидеть его в действии, сначала загрузите и установите  Node.js  (если вы этого раньше не делали). Затем выполните следующие команды:

$ git clone https://github.com/graniteds/jsonr.git
$ cd jsonr
$ node jsonr-server.js

Откройте браузер и укажите его по адресу:  http: // localhost: 8080 / index.html .

Затем взгляните на код, который содержит основную пользовательскую документацию и комментарии.

Немного объяснений:

Json-R — это попытка решить эталонную проблему с учетом следующих конструктивных ограничений:

  1. Он должен быть на 100% совместим с форматом JSON.
  2. Он должен работать с любым видом объектов JavaScript.
  3. Он не должен добавлять определенные свойства или объекты, он не должен требовать глубокого копирования.
  4. Сериализованные данные должны быть как можно меньше.
  5. Он должен использовать нативные методы JSON, когда это возможно.
  6. Оно должно быть читаемым человеком или, по крайней мере, должно быть так.

Алгоритм, используемый в Json-R, очень классический и основан на словаре объектов, созданных во время сериализации и воссозданных точно таким же образом во время десериализации.

По сути, начиная с модели города / страны выше, мы хотим создать строку JSON, которая будет выглядеть следующим образом:

[
	{
		"name": "Washington DC",
		"country": {
			"name": "USA",
			"language": "English",
			"currency": "Dollar",
			"capital": <reference to the dictionary object #1>
		}
	},
	{
		"name": "New York",
		"country": <reference to the dictionary object #2>
	},
	{
		"name": "San Francisco",
		"country": <reference to the dictionary object #2>
	}
]

Наш словарь всех различных объектов, встречающихся во время сериализации, содержит пять записей:

dictionary[0] = cities;
dictionary[1] = cities[0];
dictionary[2] = cities[0].country;
dictionary[3] = cities[1];
dictionary[4] = cities[2];

Объяснение того, как и почему этот словарь воссоздается во время десериализации точно так же, как он был создан во время сериализации, выходит за рамки данной статьи. Давайте просто скажем, что идея примерно исходит из очень упрощенного варианта  алгоритма Лемпеля-Зива-Уэлча , который используется, например, в сериализации Java и AMF3: динамический словарь элементов создается и воссоздается во время сжатия / распаковки или сериализация / десериализация.

Следующая проблема состоит в том, чтобы найти способ кодировать эти ссылки в однозначном, в то время как JSON-совместимом формате. Мы используем символы UTF-8, содержащиеся в первой «частной области использования» (т. Е. От \ uE000 до \ uF8FF), что дает нам 0xF8FF — 0xE000 = 6399 индексов. Эти символы совершенно законны и соответствуют JSON.

Стоит отметить, что эти специальные символы не назначены в спецификации UTF-8 и зарезервированы для частного, непереносимого использования. Например, символ «\ uF8FF» используется Apple для своего логотипа (символ, представляющий яблоко). Он правильно отображается как яблоко с шрифтом Apple, но может быть представлен как логотип Windows с другим шрифтом. Использование этих символов Юникода не рекомендуется, особенно в Интернете, и вы обычно не найдете их в ваших текстовых данных.

Используя эти символы в качестве словарных индексов, наши приведенные выше данные JSON-R выглядят следующим образом:

[
	{
		"name": "Washington DC",
		"country": {
			"name": "USA",
			"language": "English",
			"currency": "Dollar",
			"capital": "\uE001"
		}
	},
	{
		"name": "New York",
		"country": "\uE002"
	},
	{
		"name": "San Francisco",
		"country": "\uE002"
	}
]

Однако эти символы не экранируются в созданной строке JSON-R и будут отображаться в виде квадратов или любых специальных заполнителей символов Юникода, если вы посмотрите на них: «\ uE001» будет фактически отображаться как «and» и «\ uE002» как ‘’ (почти тот же нечитаемый глиф, в зависимости от вашего браузера). Также обратите внимание, что вы все еще можете использовать символы в диапазоне \ uE000- \ uF8FF в ваших данных: если любая строка начинается с такого символа, она будет экранирована Json-R, поэтому она не будет считаться ссылочным индексом во время десериализации (см. реализацию для деталей).

Ну и что:

Первые четыре вышеупомянутых проектных ограничения выполнены:

  1. Сериализованные данные являются действительными данными JSON.
  2. Любой объект JavaScript может быть сериализован таким образом.
  3. Реализация имеет специальный метод ‘stringify’, который создает эти данные без изменения или копирования отправленных объектов.
  4. Каждая ссылка занимает 5 байтов (1 кавычка + 3 байта для символа юникода + 1 кавычка).

Ограничение 5 выполняется только частично: мы не можем использовать собственный метод JSON.stringify, не нарушая ограничение 3. Однако мы можем использовать собственный метод JSON.parse и затем разрешать ссылки.

Ограничение 6 по-прежнему остается проблемой: нечитаемые (или даже не отображаемые) символы Юникода далеки от восприятия человеком. Вот почему JSON-R поставляется с методом showReferences, который преобразует приведенный выше текст в:

0@[
	1@{
		"name": "Washington DC",
		"country": 2@{
			"name": "USA",
			"language": "English",
			"currency": "Dollar",
			"capital": "@1"
		}
	},
	3@{
		"name": "New York",
		"country": "@2"
	},
	4@{
		"name": "San Francisco",
		"country": "@2"
	}
]

Последовательности «<index> @ [» или «<index> @ {» являются словарными статьями с соответствующими индексами. «@ <index>» — это ссылки на эти записи. Это делает вещи намного более читабельными, но это может быть очень запутанным с большим графом объектов.

Текущие ограничения:

  1. Единственная реализация на стороне сервера предназначена для Node.js: Json-R, если он имеет какой-либо успех, должен иметь реализации Java / PHP / .NET / <ваш язык>.
  2. Графы объектов не должны иметь более 6399 различных объектов (включая массивы). Это может быть легко исправлено путем кодирования ссылок с 2 или более символами после этого ограничения (например, «\ F8FF \ uE000» для 6400 и т. Д.)
  3. Только объекты принимаются во внимание. Улучшенная версия (не для удобства чтения) может также использовать словарь строк.