Pull to refresh

edn: extensible data notation

Reading time 5 min
Views 9.5K
В этой статье я хочу рассказать про 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
{:name "Jack"
 :lastname "Brown"
 :gender :male}

{"name": "Jack",
 "lastname": "Brown",
 "gender": "male"}



числа

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?»
Tags:
Hubs:
+22
Comments 30
Comments Comments 30

Articles