edn: extensible data notation

В этой статье я хочу рассказать про 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?»
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 30

    0
    Еще есть Yaml.
      0
      Который значительно сложнее и очень хрупкий (из-за значащих отступов).
        0
        Смотря что вам нужно от формата сериализации. Кто-то вон на питоне и кофе пишет, на отступы вроде не жалуются. В edn зато разделителей в массивах нет.
        Насчет сложности, искренне не понимаю чем yaml сложнее edn. Вроде всё то же самое, вид сбоку.
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Вы, видимо, не в курсе, что валидный json является в то же время валидным yaml документом?
              0
              Я был не в курсе. Забавно. но спорное преимущество. Писать yaml в json'овом формате это странно. Мне лично yaml немного не нравится тем, что в нём я не могу спокойно, как захочется, писать данные, надо строго следить за отступами. Если редактор поддерживает yaml и сам всё контролирует, то круто, но это не всегда так. Конечно в этом и прелесть yaml'а, что все файлы (конфиги) выглядят однообразно и красиво. Но если что-то на коленке протестировать, как пример с тем же curl'ом (где гарантия, что парсеры yaml'а корректно распарсят json-like формат?). Поэтому с json'ом или edn'ом мне кажется будет проще для таких целей.
                0
                >> где гарантия, что парсеры yaml'а корректно распарсят json-like формат?
                Там же где и гарантия, что они корректно распарсят yaml. Он так устроен, что json является его подмножеством (yaml 1.2).
                Если вы не доверяете конкретным реализациям парсера, то это уже другой вопрос.
                  0
                  Я сейчас попробовал найти yaml парсер для джавы, который поддерживает 1.2 и сходу не нашёл. Т.е. для спецификации, которой уже больше 3 лет нет реализации на одном из самых популярных языков? Я могу это понять тем, что это не сильно актуально, т.к. мало кто пользуется этими фичами. И следовательно от того, что они есть ни холодно, ни жарко.
                  Вообще посмотрел на оф сайте, большинство реализуют 1.0 или 1.1: www.yaml.org/
              • НЛО прилетело и опубликовало эту надпись здесь
            0
            Об этом конечно можно много спорить, но я напишу не для этого а просто чтобы ваше безапеляционное заявление не казалось читателям какой-то общеизвестной истиной а тем чем и является — вашим личным мнением, подкреплённым неизвестно чем.
            По мне так yaml гораздо проще, ведь он и задумывался как формат с наибольшей удобочитаемостью и чётким пониманием людьми. Теже значащие отступы гарантируют что структуру человек сможет легко воспринять на глаз, потому что они всегда будут соответствовать семантическому уровню данных в документе. Может вы имеете ввиду что его сложнее парсить? Ну это да, так это проблема парсеров, их всё равно почти никто на коленке не пишет…
          0
          Может ли в качестве ключа в мапе быть использован обьект?
            +1
            А что вы понимаете под объектом? В качестве ключа может использоваться что угодно, в том числе другая мапа. Возможно nil нельзя использовать.
              0
              Имел ввиду как раз «другую мапу». При работе с json приходится дико изворачиваться.

              Формат интересный, а теперь вдвойне интересный, спасибо!
                0
                Можно даже так делать
                {nil nil}
              0
              При работе с json приходится дико изворачиваться.

              {"map": {}}
              


              Где тут дикие извороты?

              Или вы хотите писать так?

              {{} : {}}
              


              Если да, то теперь самому интересно как это будет выглядеть и обрабатываться
                0
                Истины ради хочу отметить, что иногда приходится так изголятся, например, когда нужно сериализовать сложный объект в JSON. Ну а объекты в качестве ключей встречаются не так и редко, и предложение нового типа WeakMap тому доказательство.
                +1
                >edn — формат данных, появившийся из языка clojure.
                S-exp приблизительно раз в 10 старше самой clojure а plist'ы для представления данных думаю появились вместе с ними.
                  0
                  Верю. А как это относится к тому, что edn появился из clojure?
                    0
                    к тому что edn это plist
                      0
                      Я плохо разбираюсь в истории возникновения лиспов и их синтаксисов. Поэтому я просто написал, что edn пошёл из clojure. Откуда пошёл clojure это другой вопрос, и он тут не раскрывается.
                        0
                        Ну вы различайте все-таки концепцию и конкретные правила конкретного синтаксиса.
                    +1
                    Гм, я так расчитывал что хоть в одном более-менее общеизвестном формате появяться back reference.(возможность ссылаться на другой объект внутри документа)
                    Но в статье про них не упоминается, значит и тут их нет?
                      +2
                      Нет, их тут нет. Для этого видимо нужно юзать yaml.
                        0
                        В нормальной реализации plist (например в Common Lisp) есть backreference:
                        '(:param1 #1=1 :param2 2 :param3 #1#)
                          +1
                          Нет, и это философская позиция. EDN — формат представления данных, а не объектов и их связей. Backreference бы усложнили понимание, реализацию и сделали бы парсер stateful.
                            0
                            Да, пожалуй вы правы. backreference лучше реализовывать на уровне выше парсера, как-то мне это не приходило в голову
                              0
                              Удваиваю оратора выше. Сам автор напирает на это:
                              edn is a system for the conveyance of values.
                              и далее
                              …Nor is it a system for representing objects…
                              github.com/edn-format/edn
                            0
                            Можно использовать формат UTF8: \u2603.
                            Вероятно, тут имеется ввиду юникодный литерал, т.е. юникод в целом, а не именно UTF-8. Точно такие символы (четыре шестнадцатеричные цифры) поддерживаются и в JSON, и там тоже о них не говорится, как о UTF-8.

                            В качестве ключей может выступать любой другой тип.
                            К слову, в YAML ключи тоже могут быть не скалярного типа. Вероятно, как для YAML, так и для edn большинство реализаций парсера всё таки ограничиваются простыми типами.

                            Вообще сложно придумать, когда может быть удобнее использовать список, а не вектор.
                            Имхо, здесь списки по своей природе ближе к типу record, чем к типу vector. Т.е. это типа рекорда, в котором элементы не поименованы. А сложно придумать, потому что и списки и вектора здесь гетерогенные. Для многих языков второе не верно.

                            Элементы с тегами это весьма клёвая штука. Сам автор в одной из своих презентаций напирает на важность расширяемости для формата. Как edn, так и YAML поддерживают теги, что позволяет регистрировать в формате свои типы. JSON — нет, и теги приходится симулировать.

                              0
                              У меня другое имхо. Для меня вектор и список это 2 очень похожих типа данных с разными характеристиками операций над ними. И я не думаю, что тут список — это record.
                              0
                              Лично мне в edn симпатичен такой простой факт: пары ключи-значения не надо никак разделять. Ключ и значение в мапе определятся чисто по порядку: нечётные элементы это ключи, последующие чётные — их значения. Это очень круто.

                              С одной стороны мы имеем JSON с его запятой-разделителем и двоеточием внутри пары. Он также не позволяет «висячие» запятые, это означает, что при перестановке-добавлении-удалении элементов легко потерять запятую, или добавить лишнюю.
                              С другой мы имеем YAML, где всё подвязано на отступы.

                              Тут нет ни подвязки на разделитель, ни на отступы, да и разделитель внутри пары не нужен. Супер.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое