Усложнённый упрощённый JSON

    Есть такой известный и весьма простой текстовый формат 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 : nullobject
    value -> { "t": "null" }
    

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

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

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

    Определим NSNJSON представление Pnumber : numberobject
    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 : arrayobject
    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 × valueobject
    (name, value) -> { "n": name, "t": valuePresentation.t, "v": valuePresentation.v }
    

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

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

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

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

    Наконец, определим итоговое NSNJSON представление Pjson : jsonobject:
    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 : objectstring
    presentation -> presentation.t
    

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

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


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

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

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

    Определим функцию восстановления Rboolean : objectboolean
    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 : objectarray
    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 : objectobject
    presentation -> if (type == "object") { return data; }
    

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

    И в итоге, определим функцию восстановления Rjson : objectjson:
    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)!
    Спасибо за внимание!

    Similar posts

    Ads

    Comments 21

      +25
      Ничего не понял. А зачем это надо и что оно даст?
        +1
        Ну это же ненормальное программирование. Оно не должно что-то давать. Оно просто есть.

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

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

        {
          _id: ObjectId("562d2063bc0f12de6a000001")
        }
        


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

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


        И всё, теперь это валидный JSON. Написать функцию восстановления тоже не составит труда.
          +1
          Что-то не понимаю примера. Берем BJSON и перегоняем в NSNJSON только из-за того, что можем так сделать?
            +1
            :) пример демонстрирует один из способов применения. Примером я хотел показать, как передавать данные, которые не являются валидным JSON. Хотелось придумать формат, который основывался на каком-нибудь известном формате и требовал бы минимум усилий для написания драйверов для этого формата.
              +1
              Не лучше ли вместо JSON использовать более приспособленные для этого форматы?
                +2
                Можно. Почему бы нет?! :)

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


            Cледовательно,
            {
              "_id": "562d2063bc0f12de6a000001"
            }
            


            И никакой магии.

            Но уж если хочется странного, то логичнее:

            {
              "_id": {"$objectid": "562d2063bc0f12de6a000001"}
            }
            


            Тогда будет много проще написать свой сериализатор/десериализатор как функцию второго аргумента JSON.stringify(value[, replacer[, space]])/JSON.parse(text[, reviver]).
              0
              Как потом понять что это был именно ObjectId из такого объекта:
              {
                "_id": "562d2063bc0f12de6a000001"
              }
              

              ?

              Это еще один способ, да. Но лично мне не нравится это тем, что приходится явно указывать «строение» id.
                0
                А никак. Единственное полезное, что можно сделать с полем _id — это прочитать его, и, в крайнем случае, сконструировать новый такой же объект:
                ObjectId(data._id)
                


                Понятно, что вы это делаете без особого смысла, но по пути вы потеряли очень полезное, на мой взгляд, свойство JSON-а: человекочитаемость.
                Один способ, как сохранить это свойство и передать тип я уже назвал. Только что придумал еще один:
                {
                    "_id": ["objectid", "562d2063bc0f12de6a000001"],
                    "array": ["array", []]
                }
                

                {
                  "": ["null"]
                }
                


                Не так сильно раздувается размер, и не так сильно страдает читаемость.
          +2
          У вас ус отклеился везде "t": "null".
            0
            Исправлено :)
          • UFO just landed and posted this here
              0
              Так можно и одним типом string обойтись. И передавать через JSON не только невалидный JSON, но и код в любом другом формате или на языке программирования. Надо будет просто функцию восстановления написать.
              {
                  value: "................................................"
              }
              
                +1
                Мне не понравилась такая идея и я решил изобрести NSNJSON. К тому же в Вашей идее необходимо знать природу этих данных. Здесь же всё прозрачно и универсально.
                  +1
                  Можно проще: "................................................"
                  +1
                  А еще можно взять и поменять местами все буквы на клавиатуре.
                    0
                    осталось только добавить схемы и назвать jsoap
                      +2
                      Уважаемые, почему такая реакция в разделе «Ненормальное программирование»? :) Решение не предлагается для промышленного внедрения. Преследовалась совершенно простая цель. Никаких намеков на производительность и крутость! :) Разве данная статья противоречит правилам Хабра или раздела «Ненормальное программирование»? :) За что минусы-то? :)
                        0
                        Я думаю потому, что приведенное решение не только не нормально, но еще и поставленную вами же задачу не решили.
                        Вы решали задачу, как представить в JSON произвольные типы, но получилось у вас только усложнить синтаксис, а произвольные типы все еще представить не удалось (корежить человекопонятный жисон, и все равно передавать не тип, а строку — это не решение).

                        Встроить в жисон интерпретатор брейнфака, и декодировать им значения — отличная идея. Превращать старый добрый словарь в массив объектов — нет.
                          0
                          Вы решали задачу, как представить в JSON произвольные типы, но получилось у вас только усложнить синтаксис, а произвольные типы все еще представить не удалось (корежить человекопонятный жисон, и все равно передавать не тип, а строку — это не решение).


                          В чем конкретно мне не удалось решить поставленную задачу представления JSON типов? Прошу отметить, что в статье речь идет исключительно о null, true, false, number, string, array, object.

                          корежить человекопонятный жисон, и все равно передавать не тип, а строку — это не решение


                          Я если честно, не понял Ваш этот комментарий.
                            0
                            Да, каюсь, это я не внимательно прочитал, думал, что речь идет о вообще любых типах.

                            Чуть выше уже ответил: речь об удобстве чтения формата человеком.

                      Only users with full accounts can post comments. Log in, please.