Продолжая тему «Ненормальное программирование и JSON» меня посетила еще одна мысль об еще одном представлении JSON данных.

В своей прошлой статье «Усложнённый упрощённый JSON» я уже рассказал о формате NSNJSON, который позволяет представить любые JSON данные с помощью всего 4 JSON типов: number, string, array, object.

На этот раз я расскажу о способе представления любых JSON данных с помощью всего 3 типов: number, string, array.




Содержание


История
Проект «Brackets»
Реализация
Примерчики


История


После публикации статьи о формате NSNJSON я начал работать над драйверами для этого формата. Практически сразу же у меня возникла идея предоставления пользователям драйвера возможность использовать свои правила для кодирования/декодирования JSON.

В ходе работы над этой идеей я пытался придумать интересный пример пользовательских правил… И вот однажды, меня и посетила это безумная мысль под кодовым названием «Brackets».

Я подумал, что было бы интересно посмотреть на JSON данные, которые представлены с помощью строк, чисел и скобочек!

Проект «Brackets»


Для начала введем таблицу констант для JSON типов.
JSON тип number string true false array object
Маркер 1 2 3 3 4 5

Для типа null всё очень просто. Просто отображаем его на пустой массив.
null -> []

Определим представление для значения value типа string:
value -> [1, value]

Определим представление для значения value типа string:
value -> [2, value]

Определим представление для значения value типа boolean (true, false):
value -> [3, ~~value]
// [3, 1] для true
// [3, 0] для false

Остались массивы и объекты. Начну с массивов. Для массивов можно определить представление так:
array -> [4, p1, ..., pN]
// p1, ..., pN - представления для элементов массива

С объектами чуть сложнее, ведь необходимо сохранять информацию о полях объекта.
Сначала определим представление для поля объекта.
Пусть поле объекта имеет имя name и значение value. Тогда представление определим так:
name: value -> [name, valuePresentation]
// valuePresentation - представление значения value

Теперь можно определить представление для всего объекта целиком:
name: value -> [5, [name1, value1Presentation], ..., [nameN, valueNPresentation]]
// value1Presentation, ..., valueNPresentation - представления для значений value1, ..., valueN

Определив такие представления очень легко определить и алгоритм восстановления JSON.
— В случае, если мы сталкиваемся с пустым массивом, — значит встретили представление значения типа null.
— Иначе, берем первый элемент массива и ищем по нему соотвествующее имя JSON типа. Далее, драйвер в соответствии с полученным типом вызовет нужную функцию декодирования.

Реализация


Я покажу реализацию такого представления с помощью NSNJSON Node.js драйвера.

Драйвер опубликован на npmjs.com, поэтому для установки драйвера достаточно выполнить простую команду:
npm install nsnjson-driver

Перед тем, как я начну рассказывать о реализации, хочу отметить, что драйвер использует Maybe из пакета data.maybe.

Для начала определим два файла констант.

Константы маркеров (custom.format.json):
{
  "TYPE_MARKER_NUMBER":  1,
  "TYPE_MARKER_STRING":  2,
  "TYPE_MARKER_BOOLEAN": 3,
  "TYPE_MARKER_ARRAY":   4,
  "TYPE_MARKER_OBJECT":  5
}

Константы имен JSON типов (nsnjson.types.json):
{
  "NULL":    "null",
  "NUMBER":  "number",
  "STRING":  "string",
  "BOOLEAN": "boolean",
  "ARRAY":   "array",
  "OBJECT":  "object"
}

Теперь необходимо задать собственные правила представления/восстановления JSON.

custom.encoder.js
var Maybe = require('data.maybe');

var nsnjson = require('nsnjson-driver');

var Types = require('./nsnjson.types');

var Format = require('./custom.format');

var encodingOptions = {};

// Определяем реализацию правила представления для значения value типа JSON null
encodingOptions[Types.NULL] = function() {
  return Maybe.Just([]);
}

// Определяем реализацию правила представления для значения value типа JSON number
encodingOptions[Types.NUMBER] = function(value) {
  return Maybe.Just([Format.TYPE_MARKER_NUMBER, value]);
}

// Определяем реализацию правила представления для значения value типа JSON string
encodingOptions[Types.STRING] = function(value) {
  return Maybe.Just([Format.TYPE_MARKER_STRING, value]);
}

// Определяем реализацию правила представления для значения value типа JSON boolean
encodingOptions[Types.BOOLEAN] = function(value) {
  return Maybe.Just([Format.TYPE_MARKER_BOOLEAN, ~~value]);
}

// Определяем реализацию правила представления для значения array типа JSON array
encodingOptions[Types.ARRAY] = function(array) {
  var presentation = [Format.TYPE_MARKER_ARRAY];

  for (var i = 0, size = array.length; i < size; i++) {
    var itemPresentationMaybe = this.encode(array[i]);

    if (itemPresentationMaybe.isJust) {
      var itemPresentation = itemPresentationMaybe.get();

      presentation.push(itemPresentation);
    }
  }

  return Maybe.Just(presentation);
}

// Определяем реализацию правила представления для значения object типа JSON object
encodingOptions[Types.OBJECT] = function(object) {
  var presentation = [Format.TYPE_MARKER_OBJECT];

  for (var name in object) {
    if (object.hasOwnProperty(name)) {
      var valuePresentationMaybe = this.encode(object[name]);

      if (valuePresentationMaybe.isJust) {
        var valuePresentation = valuePresentationMaybe.get();

        var fieldPresentation = [name, valuePresentation];

        presentation.push(fieldPresentation);
      }
    }
  }

  return Maybe.Just(presentation);
}


custom.decoder.js
var Maybe = require('data.maybe');

var nsnjson = require('nsnjson-driver');

var Types = require('./nsnjson.types');

var Format = require('./custom.format')

var decodingOptions = {
  // Определяем функцию получения имени JSON типа из представления.
  'type': function(presentation) {
    if (presentation.length == 0) {
      return Maybe.Just(Types.NULL);
    } else {
      switch (presentation[0]) {
        case Format.TYPE_MARKER_NUMBER:  return Maybe.Just(Types.NUMBER);
        case Format.TYPE_MARKER_STRING:  return Maybe.Just(Types.STRING);
        case Format.TYPE_MARKER_BOOLEAN: return Maybe.Just(Types.BOOLEAN);
        case Format.TYPE_MARKER_ARRAY:   return Maybe.Just(Types.ARRAY);
        case Format.TYPE_MARKER_OBJECT:  return Maybe.Just(Types.OBJECT);
      }

      return Maybe.Nothing();
    }
  }
};

// Определяем восстановление значения типа JSON null
decodingOptions[Types.NULL] = function() {
  return Maybe.Just(null);
}

// Определяем восстановление значения типа JSON number
decodingOptions[Types.NUMBER] = function(presentation) {
  return Maybe.Just(presentation[1]);
}

// Определяем восстановление значения типа JSON string
decodingOptions[Types.STRING] = function(presentation) {
  return Maybe.Just(presentation[1]);
}

// Определяем восстановление значения типа JSON boolean
decodingOptions[Types.BOOLEAN] = function(presentation) {
  return Maybe.Just(presentation[1] != 0);
}

// Определяем восстановление значения типа JSON array
decodingOptions[Types.ARRAY] = function(presentation) {
  var array = [];

  for (var i = 1, size = presentation.length; i < size; i++) {
    var itemPresentation = presentation[i];

    var itemMaybe = this.decode(itemPresentation);

    if (itemMaybe.isJust) {
      var item = itemMaybe.get();

      array.push(item);
    }
  }

  return Maybe.Just(array);
}

// Определяем восстановление значения типа JSON object
decodingOptions[Types.OBJECT] = function(presentation) {
  var object = {};

  for (var i = 1, size = presentation.length; i < size; i++) {
    var fieldPresentation = presentation[i];

    var name = fieldPresentation[0];

    var valueMaybe = this.decode(fieldPresentation[1]);

    if (valueMaybe.isJust) {
      var value = valueMaybe.get();

      object[name] = value;
    }
  }

  return Maybe.Just(object);
}

module.exports = {
  decode: function(presentation) {
    return nsnjson.decode(presentation, decodingOptions);
  }
};



Ну а теперь посмотрим как этим пользоваться.
// Загружаем encoder с нашими правилами представления JSON
var customEncoder = require('./custom.encoder');

// Загружаем decoder с нашими правилами восстановления JSON
var customDecoder = require('./custom.decoder');

var data = {message: ['I', 'love', 'brackets']};

console.log('Data:', JSON.stringify(data));
// Data: { "message": [ "I", "love", "brackets" ] }

var presentationMaybe = customEncoder.encode(data);

if (presentationMaybe.isJust) {
    var presentation = presentationMaybe.get();

    console.log('Presentation:', JSON.stringify(presentation));
    // Presentation: [ 5, [ "message", [ 4, [ 2, "I" ], [ 2, "love" ], [ 2, "brackets" ] ] ] ]

    var restoredDataMaybe = customDecoder.decode(presentation);

    if (restoredDataMaybe.isJust) {
        var restoredData = restoredDataMaybe.get();

        console.log('Restored data:', JSON.stringify(restoredData));
        // Restored data: { "message": [ "I", "love", "brackets" ] }
    }
}



Примерчики


По традиции, привожу примерчики применения данного алгоритма представления JSON данных.

Простые примерчики
JSON тип null
// JSON
null

// NSNJSON
[ ]

JSON тип number
// JSON
2015

// NSNJSON
[ 1, 2015 ]

JSON тип boolean
// JSON
true

// NSNJSON
[ 3, 1 ]

JSON тип string
// JSON
"Habrahabr.ru"

// NSNJSON
[ 2, "Habrahabr.ru" ]

JSON тип array
// JSON
[ "Year", 2015 ]

// NSNJSON
[ 4, [ 2, "Year" ], [ 1, 2015 ] ]

JSON тип object
// JSON
{ "message": [ "I", "love", "brackets" ] }

// NSNJSON
[ 5, [ "message", [ 4, [ 2, "I" ], [ 2, "love" ], [ 2, "brackets" ] ] ] ]


Ну вот и подошла к концу статья о ненормальном программировании, JSON и скобочках. Спасибо за Ваше внимание!

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


  1. yatagarasu
    02.11.2015 21:40
    +9

    Вы в одном шаге от лиспа.


    1. jxcoder
      02.11.2015 21:41
      -2

      :) Вроде здорово! :)


      1. yatagarasu
        02.11.2015 23:07

        Да, ну в общем интересное занятие. Делайте уже лисп машину с синтаксисом json.


        1. vintage
          02.11.2015 23:10

          А я вот делаю лисп машину с синтаксисом tree, получается симпатишненько)


    1. YChebotaev
      02.11.2015 23:32

      Скорее, от msgpack-а.


      1. yatagarasu
        03.11.2015 00:44

        msgpack тоже в одном шаге от лиспа.


  1. vintage
    02.11.2015 21:54
    +4

    Вы сейчас стремительно изобретаете json-ast :-) Но я предлагаю пойти дальше и ограничиться исключительно теорией множеств:
    0 = []
    1 = [[]]
    2 = [[[]]]
    и так далее


    1. jxcoder
      02.11.2015 21:55

      Адовенько! :) Но, пожалуй, я остановлюсь на моих текущих наработках)


  1. Delphinum
    02.11.2015 22:48
    +3

    Судя по «примерчикам», JSON лаконичнее.


  1. hellman
    02.11.2015 23:07
    +9

    В чем смысл? Можно и строковым типом обойтись, и хранить в нём, например… нормальный json!


    1. igordata
      02.11.2015 23:40

      Brainfuck!