В этой статье я хочу рассказать про edn. edn — формат данных, появившийся из языка clojure. Он похож на JSON, но предоставляет некоторые возможности, отсутствующие в JSON. Особенности edn описаны далее. Пример для затравки:
{:name "edn"
:implementations #{"clojure" "java" "ruby" "python" "c" "javascript" "haskell" "erlang"}
:related "clojure"
:encoding :UTF-8}
Появление
История появления edn похожа на появление JSON: сначала появился язык программирования, а потом из него выделили подмножество и стали использовать в качестве формата данных. Если для JSON язык прародитель — JavaScript, то для edn — это Clojure.
Как я говорил ранее edn и JSON очень похожи и учитывая то, что JSON является сейчас наиболее известным, простым и популярным форматом данных, я буду рассказывать про edn через его отличия от JSON.
В edn поддерживаются все простые типы, присутствующие в JSON: строки, числа, булевские значения. Так же присутствуют новые:
nil
В качестве нулевого значения в edn используется значение nil
. В JSON используется null
.
знаки (characters)
edn поддерживает знаки для указания отдельных символов. Они начинаются с обратного слеша: e
, d
, n
. Можно использовать формат UTF8: u2603
. Специальные знаки прописываются полностью: newline
, return
, space
, tab
.
В JSON отдельные знаки обычно представляются в виде строки длины 1.
Я не называю знаки символами, потому что в edn есть отдельный тип symbol, который будет описан ниже.
ключевые символы (keywords)
Мне сложно сформулировать, что такое ключевые символы. Я бы сказал, что это смесь строк с перечислениями. Их удобно использовать, когда есть конечное фиксированное множество возможных значений. Эти значения и можно задавать ключевыми словами. Так же принято в качестве ключей отображений использовать ключевые символы. Ключевые символы начинаются с двоеточия: :name
, :lastname
, :female
, :green
. Те, кто работал с руби должны узнать в них символы, похожие типы присутствуют и в других языках, например common lisp.
Пример использования ключевых слов в отображении и сравнение с JSON версией:
edn | JSON |
|
|
числа
edn разделяет 2 типа чисел: целые и вещественные. Также он поддерживает числа произвольной длинны используя суффикс N
для целых и M
для вещественных:
[12345678891231231231232133N, 123.123123123123123213213M]
вектора
JSON'овский массив в edn называется вектором: последовательность значений, для которых поддерживается операция произвольного доступа. В отличие от JSON элементы не должны разделяться запятыми. Их можно опустить:
[1 2 3 "Hello" "World"]
отображения (maps)
В JSON они называются объектами. В edn ключ от значения не отделяется двоеточием (двоеточие — ошибка). Запятые тоже можно опустить. В качестве ключей может выступать любой другой тип, например числа или ключевые слова:
{:name "Jack"
:lastname "Brown"
:gender :male
42 54}
Здесь задаётся отображение с ключами :name
, :lastname
, :gender
и 42
, значения соответственно "Jack"
, "Brown"
, :male
, 54
.
множества
edn поддерживает тип данных множество. Оно задаётся в формате #{val1 val2 val3}
. Порядок в множестве не важен и парсеры не гарантируют никакого конкретного порядка. В принципе парсеры должны преобразовать в стандартный для языка программирования тип, например HashSet
для java, PersistentHashSet
для clojure и аналогично для других языков. А в этих типах данных порядок никакой и не гарантируется. Пример: зададим очень полезное отображение содержащее времена года и 3 цвета:
{:seasons #{:winter :spring :summer :autumn}
:colors #{[255 0 0] [0 255 0] [0 0 255]}}
списки
edn кроме вектора поддерживает списки. Список отличается от вектора тем, что отсутствует случайных доступ. Хотя это уже дело парсера в какой реальный тип он преобразует список. Вообще сложно придумать, когда может быть удобнее использовать список, а не вектор. Так что вектор в подавляющем числе случаев и используется. Пример списка:
(1 2 3 4 5)
символы (symbols)
В clojure символы используются для обозначений переменных. Т.е. похожи на идентификаторы в обычных языках программирования: a
, b
, i
, hello
, persons
. Символ из нескольких слов принято разделять дефисом: prime-numbers
, visited-nodes
. Они могут содержать кроме чисел и букв следующие знаки: . * + ! - _ ? $ % & =
. Мне сложно придумать способ применения симвоволов в edn, когда есть строки и ключевые символы. Это уже зависит от вашего воображения. В datomic они используются для передачи запросов, например:
[:find ?x :where [?x :foo]]
?x
— символ.
элементы с тегами (tagged elements)
edn поддерживает возможность расширений при помощи тегов. Тег — идентификатор, который начинается с #
, за которым идёт символ: #inst
, #uuid
, #myapp/Person
. Особенность таких элементов состоит в том, что парсер, когда встречает такой элемент читает его и следующий за ним элемент, передаёт специальному обработчику, который должен преобразовать входные агргументы в нужный тип и вернуть. Примеры:
#myapp/Person {:first "Fred" :last "Mertz"}
Тут в парсере должен быть зарегистрирован обработчик тега #myapp/Person
, который принимает отображение и преобразует его в объект класса myapp.Person
(если в языке есть классы), или что-то подобное.
#inst "1985-04-12T23:20:50.52Z"
Обработчик принимает строку в формате RFC-3339 и преобразует в соответствующую дату.
#uuid "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
Обработчик преобразует UUID строку в соответствующий объект.
Последние 2 тега являются встроенными и должны работать из коробки. Также накладывается ограничение, что пользовательские теги должны всегда иметь namespace в начале как в случае с #myapp/Person
: тут myapp
— namespace. Теги без namespace (например #inst
, #uuid
) зарезервированы для стандартных обработчиков.
комментарии
Для комментариев используется ;
. С помощью него можно закомментить строку:
{
:red [255 0 0] ; Red 255, green 0, blue 0
:orange [255 127 0] ; Red 255, green 127, blue 0
}
Более цельный пример edn
Приведём пример списка всех пользователей, который посетили ресурс за последние пару дней. Пример надуман и цель его в том, что продемонстрировать edn ещё раз:
[{:name "Jack"
:lastname "Brown"
:roles #{:admin :operator}
:last-visited #inst "2013-04-12T23:20:50.52Z"
:id #uuid "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"}
{:name "John"
:lastname "Black"
:roles #{:user}
:last-visited #inst "2013-04-12T20:20:50.52Z"
:id #uuid "b371b600-b175-11e2-9e96-0800200c9a66"}]
Когда использовать
Это субъективный вопрос. Все вышеперечисленные особенности можно реализовать и в JSON вводя свои собственные конструкции, но это требует более сложной логики для преобразование в/из JSON. В edn они есть из коробки, что весьма удобно. Если вы работаете с clojure, то edn является естественным выбором. Так же может вам надоел скучный JSON и хочется поработать с более гибким и настраиваемым форматом, в чём могут помочь теги. Наличие «стандартного» типа для даты тоже приятная особенность. Можно сказать, что edn — JSON на стероидах.
Ссылки
Оффициальное описание формата: github.com/edn-format/edn
Реализации для java, ruby, python, javascript, haskell и других языков: github.com/edn-format/edn/wiki/Implementations
Обсуждение на Hacker News: news.ycombinator.com/item?id=4487462 Там как раз и ругаются по поводу «Зачем edn, если есть JSON?»
Автор: Nikelandjelo