Есть такой известный и весьма простой текстовый формат JSON.

JSON формат определяет следующие типы: null, boolean (true, false), number, string, array, object.

А что, если поставить задачу о представлении любых JSON данных с помощью всего 4 типов: number, string, array, object?



Добро пожаловать в ненормальное программирование!
Гость программы: NSNJSON (Not So Normal JSON)!


Содержание


Термины и обозначения
Представления для «простых» типов
Представления для «контейнерных» типов
Восстановление JSON
Восстановление JSON для «простых» типов
Восстановление JSON для «контейнерных» типов
Примеры
Драйверы
Заключение

Попробуем подойти к данной задаче немного формально. В этой статье я буду использовать обозначения взятые с сайта json.org. И для удобства добавлю немного своих.

Термины и обозначения


Введем тип boolean = true | false.

А еще введем тип name, значения которого состовляют подмножество значений типа string — те значения, которые являются корректным именем для поля объекта.

В NSNJSON представлениях используется всего три поля:
  • ttype — тип значения (обязательное поле),
  • vvalue — значение (обязательное поле),
  • nname — имя поля (используется в представлениях полей объекта).


Представления для «простых» типов


К «простым» типам будем относить следующие: null, boolean, string, number.

Определим NSNJSON представление Pnull : null > object
value -> { "t": "null" }

Определим NSNJSON представление Ptrue : true > object
value -> { "t": "boolean", "v": 1 }

Определим NSNJSON представление Pfalse : false > object
value -> { "t": "boolean", "v": 0 }

Определим NSNJSON представление Pstring : string > object
value -> { "t": "string", "v": value }

Определим NSNJSON представление Pnumber : number > object
value -> { "t": "number", "v": value }

Определим NSNJSON представление Psimple : «простой» тип > object:
Psimple(value) = Pnull(value), если value значение типа null,
Psimple(value) = Ptrue(value), если value значение типа true,
Psimple(value) = Pfalse(value), если value значение типа false,
Psimple(value) = Pstring(value), если value значение типа string,
Psimple(value) = Pnumber(value), если value значение типа number.

Представления для «контейнерных» типов


К «контейнерным» типам будем относить следующие: array, object.

И array и object содержат элементы, поэтому необходимо:
  • во-первых, определить представление для каждого элемента,
  • во-вторых, определить итоговое представление всего «контейнера», на основе представлений его элементов.

Сперва рассмотрим «контейнеры», значения в которых имеют только «простой» тип.

Для начала разберемся с массивами, то есть значениями типа array.

Пусть массив data это значение типа array, тогда можно представить массив следующим образом:
data = (e1, ..., en),
где для всех i = 1, ..., n,
  n — длина массива,
  ei — элемент массива — значение «простого» типа.

Применим функцию представления Psimple к каждому элементу массива data.

dataElementsPresentation = (Psimple(e1), ..., Psimple(en)).

Определим NSNJSON представление Parray : array > object
data -> { "t": "array", "v": dataElementsPresentation }

Теперь разберемся с объектами, то есть значениями типа object.

Пусть объект data это значение типа object, тогда можно представить объект следующим образом:
data = { (name1, value1), ..., (namen, valuen) },
где для всех i = 1, ..., n,
  n — количество полей объекта,
  (namei, valuei) — поле объекта,
    namei — имя поля
    valuei — значение поля — значение «простого» типа.

В JSON спецификации сказано, что объект это неупорядоченный набор полей, однако в каждом конкретном случае, мы всегда можем перебрать все поля объекта и пронумеровать их в порядке перебора. Воспользовавшись таким приемом, монжно представить объект data в качестве массива полей без потери общности.

Пусть valuePresentation — результат применения функции представления Psimple к значению value.

Определим NSNJSON представление Pfield : name ? value > object
(name, value) -> { "n": name, "t": valuePresentation.t, "v": valuePresentation.v }

Применим функцию представления Pfield к каждому поля объекта data.

dataFieldsPresentation = (Pfield(name1, value1), ..., Pfield(namen, valuen)).

Определим NSNJSON представление Pobject : object > object
data -> { "t": "object", "v": dataElementsPresentation }

Определим NSNJSON представление Pcontainer : «контейнерный» тип > object:
Pcontainer(value) = Parray(value), если value значение типа array,
Pcontainer(value) = Pobject(value), если value значение типа object.

Наконец, определим итоговое NSNJSON представление Pjson : json > object:
Pjson(value) = Psimple(value), если value значение «простого» типа,
Pjson(value) = Pcontainer(value), если value значение «контейнерного» типа.

Если теперь в алгоритмах представления для «контейнерных» типов вместо функции Psimple использовать функцию Pjson, то получим функции представления, которые могут работать с «контейнерами», содержащими любые JSON значения.

Таким образом была построена схема представления любых корректных JSON данных с помощью всего четырех JSON типов: number, string, array, object.

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


У нас есть JSON, мы из него теперь можем получить NSNSJON.
Но можно ли теперь восстановить исходный JSON?
Можно.

Определим функцию получения типа JSON значения Ptype : object > string
presentation -> presentation.t

Пусть type — результат применения функции Ptype к представлению presentation.

Восстановление JSON для «простых» типов


Определим функцию восстановления Rnull : object > null
presentation -> if (type == "null") { return null; }

Определим функцию восстановления Rnumber : object > string
presentation -> if (type == "string") { return presentation.v; }

Определим функцию восстановления Rnumber : object > number
presentation -> if (type == "number") { return presentation.v; }

Определим функцию восстановления Rboolean : object > boolean
presentation -> if (type == "boolean") { return presentation.v != 0; }

Определим функцию восстановления Rsimple : object > «простой» тип
Rsimple(presentation) = Rnull(presentation), если type = «null»,
Rsimple(presentation) = Rstring(presentation), если type = «string»,
Rsimple(presentation) = Rnumber(presentation), если type = «number».
Rsimple(presentation) = Rboolean(presentation), если type = «boolean»,

Восстановление JSON для «контейнерных» типов


Также как и ранее, сперва рассмотрим «контейнеры», значения в которых имеют только «простой» тип.

Пусть имеется представление presentation массива data.

Применим функцию восстановления Rsimple к каждому элементу массива presentation.v.
data = (e1, ..., en),
где для всех i = 1, ..., n,
  ei — Rsimple(presentation.v[i]) — элемент массива.

Определим функцию восстановления Rarray : object > array
presentation -> if (type == "array") { return data; }

Пусть теперь имеется представление presentation объекта data.

Применим функцию восстановления Rsimple к каждому элементу массива presentation.v и используя значение поля n восстановим поля объекта.
data = (e1, ..., en),
где для всех i = 1, ..., n,
  ei — (namei, valuei) — поле объекта,
    namei — presentation.v[i].n — имя поля
    valuei — Rsimple(presentation.v[i]) — значение поля.

Определим функцию восстановления Robject : object > object
presentation -> if (type == "object") { return data; }

Определим функцию восстановления Rcontainer : object > «контейнерный» тип
Rcontainer(presentation) = Rarray(value), если type = «array»,
Rcontainer(presentation) = Robject(value), если type = «object».

И в итоге, определим функцию восстановления Rjson : object > json:
Rjson(presentation) = Rsimple(presentation), если восстанавливается значение «простого» типа,
Rjson(presentation) = Rcontainer(presentation), если восстанавливается значение «контейнерного» типа.

Если теперь в алгоритмах восстановления для «контейнерных» типов вместо функции Rsimple использовать функцию Rjson, то получим функции представления, которые могут работать с «контейнерами», содержащими любые JSON значения.

Примеры


В заключении, хотелось бы показать несколько простых примеров демонстрирующих применение формата NSNJSON.

примерчики
JSON NSNJSON (Not So Normal JSON)
null
{ "t": "null" }
true
{ "t": "boolean", "v": 1 }
false
{ "t": "boolean", "v": 0 }
213
{ "t": "number", "v": 213 }
"Habrahabr"
{ "t": "string", "v": "Habrahabr" }
[
  null,
  true,
  false,
  213,
  "Habrahabr"
]
{
  "t": "array",
  "v": [
    { "t": "null" },
    { "t": "boolean", "v": 1 },
    { "t": "boolean", "v": 0 },
    { "t": "string", "v": "Habrahabr" }
  ]
}
[
  {
    "userId": 17,
    "status": "online"
  },
  {
    "userId": 19,
    "status": "offline"
  }
]
{
  "t": "array",
  "v": [
    {
      "t": "object",
      "v": [
        {
          "n": "userId",
          "t": "number",
          "v": 17
        },
        {
          "n": "status",
          "t": "string",
          "v": "online"
        }
      ]
    },
    {
      "t": "object",
      "v": [
        {
          "n": "userId",
          "t": "number",
          "v": 19
        },
        {
          "n": "status",
          "t": "string",
          "v": "offline"
        }
      ]
    }
  ]
}
{
    "null_field": null,
    "true_field": true,
    "false_field": false,
    "number_field": 213,
    "string_field": "Habrahabr",
    "array_field": [
        "JSON",
        "NSNJSON"
    ]
}
{
  "t": "object",
  "v": [
    {
      "n": "null_field",
      "t": "null"
    },
    {
      "n": "true_field",
      "t": "boolean",
      "v": 1
    },
    {
      "n": "false_field",
      "t": "boolean",
      "v": 0
    },
    {
      "n": "number_field",
      "t": "number",
      "v": 213
    },
    {
      "n": "string_field",
      "t": "string",
      "v": "Habrahabr"
    },
    {
      "n": "array_field",
      "t": "array",
      "v": [
        {
          "t": "string",
          "v": "JSON"
        },
        {
          "t": "string",
          "v": "NSNJSON"
        }
      ]
    }
  ]
}



Драйверы


Я сделал два небольших драйвера для желающих поиграться с данным форматом.

Драйверы доступны на GitHub на странице проекта: nsnjson!
Драйверы:

Хорошая новость для любителей npmjs.com!
Теперь Node.js драйвер доступен и там!

А если Вы хотите попробовать NSNJSON с помощью Java драйвера, то на странице проекта Вы сможете найти инструкцию как настроить Maven файлик pom.xml, чтобы не скачивать драйвер вручную! :)

Заключение


Мне осталось напомнить, что сегодня гостем программы «Ненормальное программирование» был NSNJSON (Not So Normal JSON)!
Спасибо за внимание!

Комментарии (15)


  1. TheRabbitFlash
    25.10.2015 21:19
    +25

    Ничего не понял. А зачем это надо и что оно даст?


    1. jxcoder
      25.10.2015 21:49
      +1

      Ну это же ненормальное программирование. Оно не должно что-то давать. Оно просто есть.

      В качестве одно из примеров применения могу предложить передачу объектов, содержащие значения не входящие в спецификацию JSON. Для простоты возьму ObjectId из MongoDB.

      Вот берем какой-нибудь документик:

      {
        _id: ObjectId("562d2063bc0f12de6a000001")
      }
      


      Этот документ, не является валидным JSON.

      А мы возьмём и преобразуем его в JSON-формат:
      {
        "t": "object",
        "v": [{
          "n": "_id",
          "t": "objectid",
          "v": "562d2063bc0f12de6a000001"
        }]
      }
      


      И всё, теперь это валидный JSON. Написать функцию восстановления тоже не составит труда.


      1. RPG18
        25.10.2015 22:15
        +1

        Что-то не понимаю примера. Берем BJSON и перегоняем в NSNJSON только из-за того, что можем так сделать?


        1. jxcoder
          25.10.2015 22:19
          +1

          :) пример демонстрирует один из способов применения. Примером я хотел показать, как передавать данные, которые не являются валидным JSON. Хотелось придумать формат, который основывался на каком-нибудь известном формате и требовал бы минимум усилий для написания драйверов для этого формата.


          1. vintage
            26.10.2015 00:06
            +1

            Не лучше ли вместо JSON использовать более приспособленные для этого форматы?


            1. jxcoder
              26.10.2015 06:40
              +2

              Можно. Почему бы нет?! :)

              Я ставил задачу реализовать все множество JSON на основе лишь его подмножества. И более-менее формально описал процесс. Не нужно думать, что я предлагаю какое-то новое решение для промышленных задач. Вовсе нет. Просто была идея и просто она реализованна! :)


  1. middle
    25.10.2015 21:34
    +2

    У вас ус отклеился везде "t": "null".


    1. middle
      25.10.2015 21:51

      Исправлено :)


  1. stychos
    26.10.2015 01:42

    Напомнило serialize() из php =)


  1. michael_vostrikov
    26.10.2015 08:44

    Так можно и одним типом string обойтись. И передавать через JSON не только невалидный JSON, но и код в любом другом формате или на языке программирования. Надо будет просто функцию восстановления написать.

    {
        value: "................................................"
    }
    


    1. jxcoder
      26.10.2015 09:03
      +1

      Мне не понравилась такая идея и я решил изобрести NSNJSON. К тому же в Вашей идее необходимо знать природу этих данных. Здесь же всё прозрачно и универсально.


    1. vintage
      26.10.2015 15:18
      +1

      Можно проще: "................................................"


  1. Crank
    26.10.2015 09:32
    +1

    А еще можно взять и поменять местами все буквы на клавиатуре.


  1. fogone
    26.10.2015 12:39

    осталось только добавить схемы и назвать jsoap


  1. jxcoder
    26.10.2015 12:43
    +2

    Уважаемые, почему такая реакция в разделе «Ненормальное программирование»? :) Решение не предлагается для промышленного внедрения. Преследовалась совершенно простая цель. Никаких намеков на производительность и крутость! :) Разве данная статья противоречит правилам Хабра или раздела «Ненормальное программирование»? :) За что минусы-то? :)