Pull to refresh
Selectel
IT-инфраструктура для бизнеса

Библиотека сериализации в JSON для Erlang

Reading time3 min
Views6.7K
Поскольку мы очень активно используем opensource решения в своей деятельности, вполне естественным является и обратный процесс — публикация под свободными лицензиями библиотек и компонент, созданных в нашей компании.

В этот раз мы публикуем библиотеку сериализации в JSON типов данных Erlang, авторства si14 под BSD 2-clause license. Те проекты, для которых написана эта библиотека, ещё не готовы (ждите анонсов к осени), но библиотека уже стала вполне самостоятельной и может применяться в множестве других случаев. Традиционно, рассчитываем на кооперацию в совершенствовании, с интересом услышим о применении в других проектах.

В дебри Erlang'а

В отличие от многих динамических языков, в Erlang'е есть опциональные аннотации типов для функций и record'ов. На текущий момент они используются минимум 3 утилитами: edoc (формирует документацию из исходников; пример получаемой документации можно увидеть, например, здесь), что более важно, dialyzer (анализирует существующую информацию о типах и сообщает об ошибках несоответствия типов, в том числе несоответствия декларируемого и выведенного типов) и PropEr (система автоматической генерации тестов на основании информации о типах и декларативно задаваемых свойств функций). Использование этих деклараций стало правилом хорошего тона, поэтому почти все качественные проекты на Erlang'е имеют их. Нельзя ли использовать информацию о типах где-либо ещё?

JANE

В процессе разработки одного из проектов возникла идея: почему бы не использовать существующую информацию о типах прямо в JS (например, для отрисовки форм или валидации данных)? Блиц-опрос знакомых разработчиков подтвердил, что идея «висит в воздухе», но стандартного решения нет. Тогда появился JANE: попытка описать стандарт кодирования информации о record'ах с помощью JSON, с которым достаточно удобно работать из JS. Особенно хорошо JANE сочетается с BERT, позволяя почти прозрачно работать в JS с Erlang'овскими термами.

Формат и текущая реализация

Текущая реализация формата представляет из себя исполняемый escript, принимающий пути к .hrl файлам с описаниями record'ов и записывающий результирующие .json файлы в папку priv/records. Каждое определение record'а в файле кодируется как элемент словаря верхнего уровня с ключём, равным имени record'а и словарём, описывающим данный record, в качестве значения.
Описание конкретного record'а представляет из себя словарь с названием поля в качестве ключа и описанием данного поля в качестве значения.
Описание поля представляет из себя словарь с обязательным ключём type, содержащим спецификацию типа, и опциональным ключём default, заданным, если в спецификации record'а для поля указано значение по умолчанию.
Спецификация типа представляет из себя словарь с ключём, равным названию типа, и значением, равным списку аргументов типа (который также может содержать спецификации типов).
По умолчанию все типы полей в record'ах определяются как union их заданного типа и атома undefined. Это не всегда удобно, поэтому текущая реализация принимает параметр ignore_undefined, игнорируя при его наличии undefined.
Пример использования в качестве post-compile hook'а rebar'а:
{post_hooks, [{'compile', './priv/recordparser ignore_undefined include/test.hrl'}]}.

Примеры

Определение record'а:

-record(params_ping, {host :: nonempty_string()}).
-record(params_tcp, {host :: list(atom()),
                     port = 80 :: pos_integer(),
                     timeout :: pos_integer()}).

Результат трансляции в .json (с ignore_undefined):

{
    "params_ping": {
        "host": {
            "type": {
                "nonempty_string": []
            }
        }
    },
    "params_tcp": {
        "host": {
            "type": {
                "list": [
                    {
                        "atom": []
                    }
                ]
            }
        },
        "port": {
            "type": {
                "pos_integer": []
            },
            "default": 80
        },
        "timeout": {
            "type": {
                "pos_integer": []
            }
        }
    }
}

То же, но без ignore_undefined:

{
    "params_ping": {
        "host": {
            "type": {
                "union": [
                    {
                        "atom": [
                            "undefined"
                        ]
                    },
                    {
                        "nonempty_string": []
                    }
                ]
            }
        }
    },
    "params_tcp": {
        "host": {
            "type": {
                "union": [
                    {
                        "atom": [
                            "undefined"
                        ]
                    },
                    {
                        "nonempty_string": []
                    }
                ]
            }
        },
        "port": {
            "type": {
                "pos_integer": []
            },
            "default": 80
        },
        "timeout": {
            "type": {
                "union": [
                    {
                        "atom": [
                            "undefined"
                        ]
                    },
                    {
                        "pos_integer": []
                    }
                ]
            }
        }
    }
}


Ссылки и люди

Код библиотеки в нашем репозитории на Github'е: github.com/selectel/jane
Автор библиотеки: si14.
Традиционно, спасибо akme за согласие на BSD-license.
Tags:
Hubs:
+37
Comments14

Articles

Information

Website
selectel.ru
Registered
Founded
Employees
501–1,000 employees
Location
Россия
Representative
Влад Ефименко