Obsah
1. Formát EDN: extensible data notation
2. Sémantické rozdíly mezi formáty EDN, JSON a XML
3. Základní datové typy podporované formátem EDN
5. Kolekce, které lze v EDN použít
7. Štítky jako prostředek pro rozšíření formátu EDN
8. Podpora formátu EDN v různých programovacích jazycích
13. Převod mezi formátem JSON a EDN v Pythonu a Clojure
14. Převod mezi formátem EDN a JSON v Pythonu a Clojure
15. Převod mezi souborem .properties a formátem EDN v Clojure
16. Převod mezi XML a EDN (plná varianta zachovávající vlastnosti XML)
17. Převod XML do různých forem formátu EDN s využitím knihovny Tupelo.forest
18. Práce s formátem EDN v jazyce Go, vlastní štítky rozšiřující možnosti EDN
19. Repositář s demonstračními příklady
1. Formát EDN: extensible data notation
V dnešním článku se seznámíme se základními vlastnostmi datového formátu pojmenovaného EDN, což je zkratka celého názvu Extensible Data Notation. Tento formát, který je primárně určený pro přenos strukturovaných dat mezi různými systémy a službami, vychází ze syntaxe a sémantiky programovacího jazyka Clojure, je tedy založen na původních S-výrazech rozšířených o možnost zápisu map (slovníků), množin a vektorů. Popis tohoto formátu (a tím pádem i popis syntaxe Clojure, resp. jeho podmnožiny) naleznete na stránce https://github.com/edn-format/edn, nás však v dnešním článku bude zajímat i porovnání EDN s dalšími často používanými datovými formáty JSON a XML a taktéž to, jak lze EDN využít i v dalších programovacích jazycích; nejenom v samotném Clojure. Popíšeme si tudíž podporu EDN v Pythonu a v jazyce Go; ve skutečnosti je však EDN podporován i v Rustu či v Javě.

Obrázek 1: Logo programovacího jazyka Clojure.
(crux/q (crux/db node) '{:find [p1] :where [[p1 :name n] [p1 :last-name n] [p1 :name name]] :in [name]} "Pavel")
(crux/q (crux/db node) '{:find [(sum ?heads) (min ?heads) (max ?heads) (count ?heads) (count-distinct ?heads)] :where [[(identity [["Cerberus" 3] ["Medusa" 1] ["Cyclops" 1] ["Chimera" 1]]) [[?monster ?heads]]]]})
2. Sémantické rozdíly mezi formáty EDN, JSON a XML
V současnosti se pro přenos strukturovaných informací (ale popř. i tabulek) používá relativně velké množství různých datových formátů. Pravděpodobně nejznámějšími formáty jsou XML a JSON, ovšem vznikají a postupně jsou adaptovány i další formáty, ať již textové (YAML, edn) či binární (BSON, B-JSON, Smile, Protocol-Buffers). Je velmi pravděpodobné, že se JSON i XML bude i nadále poměrně masivně využívat, na druhou stranu je dobré znát i některá omezení těchto formátů, popř. vztah JSONu a XML k formátu EDN. Nejedná se nám přitom o syntaktické rozdíly – ty jsou viditelné na první pohled a každý dovede tyto rozdíly rozeznat (i když například absence čárek v EDN je krokem kupředu). Důležitější jsou rozdíly sémantické a pravě zde můžeme nalézt celou řadu různých omezení.
Nejvíce podobností nalezneme mezi formáty EDN a JSONem. JSON má ovšem několik omezení, a to jak v podporovaných základních datových typech (chybí například celá čísla), tak i v podpoře kolekcí (jen pole a mapa) a v rozšiřitelnosti (výhodné by například bylo rozšíření o typ pro jednoznačnou reprezentaci časových razítek). Pravděpodobně nejdůležitějším rozdílem je chybějící sémantika pro uložení množin a taktéž omezení klíčů v mapách – musí se totiž jednat o řetězce, zatímco ve formátu EDN může být klíč jakoukoli hodnotou – tedy například mapou, vektorem (relativně často používáno) nebo dokonce lze použít nil.
V předchozím textu jsem několikrát použil sousloví „formát XML“, i když se mnohdy setkáme spíše s označením „jazyk XML“ (které je ovšem nepřesné). XML, i když se jedná o značkovací jazyk (markup language) se totiž začal používat i pro přenosy strukturovaných netextových dat, což se může při zpětném pohledu znát poněkud zvláštní, neboť pro tento účel není příliš uzpůsoben (ale už fakt, že se XML podařilo „ohnout“ něco říká o jeho flexibilitě). Sémantika XML se v mnoha ohledech odlišuje jak od JSONu, tak i od EDN, protože je v něm nutné datové struktury reprezentovat neexplicitně – celý XML dokument tvoří stromovou strukturu a pouze z popisu jednotlivých značek, popř. jejich atributů vyplyne, jaká konkrétní struktura je vlastně v XML dokumentu uložena, tj. zda se jedná o seznam, asociativní pole či o množinu (navíc i typ jednotlivých prvků je nutné popsat externě). Ovšem například v případě množiny a asociativního pole se musí (externě) kontrolovat integrita datové struktury.
3. Základní datové typy podporované formátem EDN
V datech reprezentovaných ve formátu EDN lze využít hned několik datových typů, z nichž mnohé jsou známé i z dalších podobně koncipovaných formátů (textové JSON, popř. binární formáty). Jedná se o následující osmici základních datových typů:
# | Typ | Popis | Příklad |
---|---|---|---|
1 | nil | symbolizuje neexistující hodnotu | nil |
2 | boolean | pravdivostní | true, false |
3 | keyword | jednoznačný symbol (ovšem ne řetězec) | :test |
4 | string | běžný řetězec v Unicode | „hello\n“ |
5 | character | jediný znak | \newline, \c |
6 | symbol | použití pro identifikátory, včetně specifikace jmenného prostoru | foo/bar |
7 | integer | celá čísla (64 bitů se znaménkem) | 42 |
8 | floating point numbers | hodnoty s plovoucí řádovou čárkou (double) | 1.3e-10 |
4. Řetězce versus keywords
Na chvíli se zastavme u datového typu keyword. Překlad tohoto slova je poněkud problematický kvůli jeho dvojímu významu (alespoň v programování), takže se pokusím používat sousloví „klíčová hesla“, protože termín „keywords“ v jazyce Clojure i ve formátu EDN neznamená, že by se jednalo o rezervovaná klíčová slova jazyka. Klíčová hesla, která reprezentují samy sebe (svoje označení) jsou na použití jednodušší než symboly či řetězce, protože jim nemůže být přiřazena žádná hodnota. Na co se tedy vlastně v praxi tento typ formy hodí? Jedním z důvodů zavedení tohoto typu formy do programovacího jazyka Clojure i do formátu EDN byla podpora pro datového typu (kolekce) mapa (asociativní pole), v němž je možné uchovávat dvojice klíč:hodnota. A jako klíče jsou s výhodou používána právě klíčová hesla, protože jejich hodnotu nelze měnit a navíc se jejich hešovací hodnota (zkráceně heš) může vypočítat pouze jedenkrát.
Příklad použití tohoto typu:
{ :description "FIXME: write description" :url "http://example.com/FIXME" :license {...} :dependencies [...] :plugins [...] :project-edn {...} :target-path "target/%s" :profiles {...} }
{ "description" "FIXME: write description" "url" "http://example.com/FIXME" "license" {...} "dependencies" [...] "plugins" [...] "project-edn" {...} "target-path" "target/%s" "profiles" {...} }
5. Kolekce, které lze v EDN použít
Největší sémantický rozdíl mezi EDN a dalšími formáty spočívá v tom, jaké kolekce jsou podporovány. Přitom zde pod pojmem kolekce myslíme datovou strukturu, která je používána jako kontejner pro další hodnoty – pochopitelně je dovoleno rekurzivní využití kolekcí v jiné kolekci (naprosto stejně, jako je tomu v JSONu, ovšem s tím rozdílem, že JSON podporuje jen mapy a pole/vektory). Ve formátu EDN se používají čtyři typy kolekcí: seznamy (list), vektory (vector), množiny (set) a mapy map neboli asociativní pole. Všechny čtyři typy složených forem, ať již se jedná o seznam, vektor, množinu či mapu, jsou z obou stran uvozeny závorkami, přičemž musí být zachována párovost závorek (ke každé otevírací závorce přísluší jedna závorka uzavírací), která je kontrolována při načítání dat uložených v EDN.
Prvky seznamů se již tradičně (více než padesát let!) zapisují do kulatých závorek. Pro zápis vektorů se používají hranaté závorky, mapy využívají závorky složené a množiny taktéž závorky složené, ovšem před otevírací závorkou se musí napsat křížek (hash, #). Jednotlivé prvky jsou od sebe odděleny bílým znakem. V následující tabulce jsou vypsány všechny čtyři typy složených forem:
Typ kolekce | Zápis |
---|---|
Seznam | (prvky) |
Vektor | [prvky] |
Mapa | {dvojice klíč-hodnota} |
Množina | #{unikátní prvky} |
Nyní by tedy měl být následující datový soubor dobře čitelný. Jedná se o mapu s několika prvky, jejichž hodnoty jsou buď reprezentovány řetězci, vektory nebo dalšími mapami:
{ :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.1"] [tupelo "21.04.13"]] :target-path "target/%s" :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}}) }
6. Klíče
Již ve druhé kapitole jsme si řekli, že ve formátu EDN lze v mapách použít jako klíče naprosto libovolné hodnoty, což si můžeme velmi snadno demonstrovat v interaktivní smyčce REPL jazyka Clojure. Dokonce si ani nemusíte jazyk Clojure instalovat, protože postačuje příklady otestovat na stránce https://clojurescript.io/:
user=> {:foo "bar"} {:foo "bar"} user=> {42 "answer"} {42 "answer"} user=> {[1 2 3] "a vector"} {[1 2 3] "a vector"} user=> {{:type :map} "map is a key"} {{:type :map} "map is a key"} user=> {{:another-key [1 2 3]} "strange key"} {{:another-key [1 2 3]} "strange key"} user=> {nil "nil as a key"} {nil "nil as a key"}
user=> {{:another-key [1 2 3]}, "strange key"} {{:another-key [1 2 3]} "strange key"}
7. Štítky jako prostředek pro rozšíření formátu EDN
Důvodem, proč se v názvu formátu (a to hned na jeho začátku) objevuje slovo „Extensible“ je fakt, že se skutečně jedná o rozšiřitelný formát. Není sice možné rozšiřovat množství podporovaných kolekcí (ovšem na druhou stranu – pravděpodobně žádná známá kolekce nechybí, až na obecný graf), ale zato lze rozšiřovat počet datových typů. Pro tento účel se používají takzvané štítky (tags), které začínají znakem # (hash). Již v základní variantě EDN existují dva takové rozšiřující datové typy.
Prvním z těchto typů je časové razítko podle RFC-3339:
#inst "1985-04-12T23:20:50.52Z"
To je velmi užitečné, ostatně právě v oblasti dat a časových razítek existují zmatky.
Druhým z těchto typů je UUID, který se dnes skutečně využívá univerzálně:
#uuid "01234567-89ab-cdef-0123-456789abcdef"
Použitím štítků se budeme podrobněji zabývat ve druhém pokračování dnešního článku – jedná se totiž o relativně rozsáhlé a přitom užitečné téma.
8. Podpora formátu EDN v různých programovacích jazycích
Formát EDN sice není rozšířený do takové míry, jako formáty XML a JSON, ovšem i přesto ho lze použít v relativně velkém množství programovacích jazyků. Jedná se v první řadě o jazyk Clojure, což ovšem není nijak překvapivé, neboť EDN je prakticky celý odvozen od Clojure (až na štítky). Kromě Clojure je možné EDN použít v Pythonu, Javě, JavaScriptu, Rustu, jazyku Go a s velkou pravděpodobností i v dalších jazycích (ovšem již zmíněné jazyky pokrývají velké množství aplikačních použití). Podporu pro formát EDN nalezneme i v projektu Babashka, s nímž jsme se již na stránkách Roota ve stručnosti seznámili.
Knihovny pro práci s EDN pro jednotlivé jazyky:
# | Programovací jazyk | Knihovna |
---|---|---|
1 | Clojure | clojure.edn (standardní) |
2 | Python | edn_format |
3 | Java | edn-java |
4 | JavaScript | jsedn |
5 | Rust | edn-rs |
6 | Go | go-edn/edn |
7 | Babashka | clojure.edn (standardní) |
9. Clojure
Podpora formátu EDN v programovacím jazyku Clojure je „nativní“, což znamená, že všechny potřebné funkce jsou přímo součástí standardní knihovny tohoto jazyka. Konkrétně se jedná o balíček (nebo možná přesněji řečeno jmenný prostor) clojure.edn, ve kterém nalezneme pouhé dvě funkce:
# | Funkce | Stručný popis funkce |
---|---|---|
1 | read | načtení dat ve formátu EDN z libovolného vstupního proudu |
2 | read-string | načtení dat ve formátu EDN z řetězce (získaného libovolným postupem) |
Jak je v ekosystému programovacího jazyka Clojure zvykem, lze nápovědu k těmto funkcím získat přímo z interaktivního prostředí vybaveném smyčkou REPL. Nejdříve je ovšem nutné příslušnou knihovnu načíst:
user=> (require '[clojure.edn :as edn])
Nápověda k funkci read:
user=> (doc edn/read) ------------------------- clojure.edn/read ([] [stream] [opts stream]) Reads the next object from stream, which must be an instance of java.io.PushbackReader or some derivee. stream defaults to the current value of *in*. Reads data in the edn format (subset of Clojure data): http://edn-format.org opts is a map that can include the following keys: :eof - value to return on end-of-file. When not supplied, eof throws an exception. :readers - a map of tag symbols to data-reader functions to be considered before default-data-readers. When not supplied, only the default-data-readers will be used. :default - A function of two args, that will, if present and no reader is found for a tag, be called with the tag and the value.
Nápověda k funkci read-string:
user=> (doc edn/read-string) ------------------------- clojure.edn/read-string ([s] [opts s]) Reads one object from the string s. Returns nil when s is nil or empty. Reads data in the edn format (subset of Clojure data): http://edn-format.org opts is a map as per clojure.edn/read
Možná se nyní ptáte, jakým způsobem se vlastně zapisují data ve formátu EDN. Odpověď je jednoduchá – výpis jakékoli datové struktury jazyka Clojure či jakékoli hodnoty do čitelného formátu již odpovídá formátu EDN. To znamená, že pro tyto účely lze použít například standardní funkci prn-str, jejíž výsledek se uloží do souboru funkcí spit atd.:
user=> (doc prn-str) ------------------------- clojure.core/prn-str ([& xs]) prn to a string, returning it
(defproject edn2json "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.1"] [org.clojure/data.json "2.2.0"]] :plugins [[lein-project-edn "0.3.0"]] :project-edn {:output-file "details.edn"} :main ^:skip-aot edn2json.core :target-path "target/%s" :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
Podobně soubory deps.edn podporované od Clojure 1.9 mají tento formát:
{:deps {net.mikera/core.matrix {:mvn/version "0.62.0"}}}
Do souborů ve formátu EDN lze vyexportovat i detailní informace o projektu, což zajišťuje plugin lein-project-edn:
{:aliases {"downgrade" "upgrade"}, :checkout-deps-shares [:source-paths :test-paths :resource-paths :compile-path "#'leiningen.core.classpath/checkout-deps-paths"], :clean-targets [:target-path], :compile-path "/home/ptisnovs/src/presentations/edn/edn2json/target/default/classes", :dependencies ([org.clojure/clojure "1.10.1"] [org.clojure/data.json "2.2.0"] [nrepl/nrepl "0.7.0" :exclusions ([org.clojure/clojure])] [clojure-complete/clojure-complete "0.2.5" :exclusions ([org.clojure/clojure])] [venantius/ultra "0.6.0"]), :deploy-repositories [["clojars" {:url "https://repo.clojars.org/", :password :gpg, :username :gpg}]], :description "FIXME: write description", :eval-in :subprocess, :global-vars {}, :group "edn2json", :jar-exclusions ["#\"^\\.\"" "#\"\\Q/.\\E\""], :jvm-opts ["-XX:-OmitStackTraceInFastThrow" "-XX:+TieredCompilation" "-XX:TieredStopAtLevel=1"], :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0", :url "https://www.eclipse.org/legal/epl-2.0/"}, :main edn2json.core, :monkeypatch-clojure-test false, :name "edn2json", :native-path "/home/ptisnovs/src/presentations/edn/edn2json/target/default/native", :offline? false, :pedantic? ranges, :plugin-repositories [["central" {:url "https://repo1.maven.org/maven2/", :snapshots false}] ["clojars" {:url "https://repo.clojars.org/"}]], :plugins ([lein-project-edn/lein-project-edn "0.3.0"] [venantius/ultra "0.6.0"] [lein-kibit/lein-kibit "0.1.8"]), :prep-tasks ["javac" "compile"], :profiles {:uberjar {:aot [:all], :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}, :whidbey/repl {:dependencies [[mvxcvi/whidbey "RELEASE"]], :repl-options {:init (do nil (clojure.core/require 'whidbey.repl) (whidbey.repl/init! nil)), :custom-init (do nil (whidbey.repl/update-print-fn!)), :nrepl-context {:interactive-eval {:printer whidbey.repl/render-str}}}}}, :project-edn {:output-file "details.clj"}, :release-tasks [["vcs" "assert-committed"] ["change" "version" "leiningen.release/bump-version" "release"] ["vcs" "commit"] ["vcs" "tag"] ["deploy"] ["change" "version" "leiningen.release/bump-version"] ["vcs" "commit"] ["vcs" "push"]], :repl-options {:init (do (do (clojure.core/require 'ultra.hardcore) (clojure.core/require 'whidbey.repl) (whidbey.repl/init! nil) (ultra.hardcore/configure! {:repl {:print-meta false, :map-delimiter "", :print-fallback :print, :sort-keys true}})))}, :repositories [["central" {:url "https://repo1.maven.org/maven2/", :snapshots false}] ["clojars" {:url "https://repo.clojars.org/"}]], :resource-paths ("/home/ptisnovs/src/presentations/edn/edn2json/dev-resources" "/home/ptisnovs/src/presentations/edn/edn2json/resources"), :root "/home/ptisnovs/src/presentations/edn/edn2json", :source-paths ("/home/ptisnovs/src/presentations/edn/edn2json/src"), :target-path "/home/ptisnovs/src/presentations/edn/edn2json/target/default", :test-paths ("/home/ptisnovs/src/presentations/edn/edn2json/test"), :test-selectors {:default (constantly true)}, :uberjar-exclusions ["#\"(?i)^META-INF/[^/]*\\.(SF|RSA|DSA)$\""], :url "http://example.com/FIXME", :version "0.1.0-SNAPSHOT"}
10. Python
V programovacím jazyku Python je formát EDN dostupný přes balíček nazvaný přímočaře edn_format. Tento balíček nainstalujete jednoduše přes pip:
$ pip install --user edn_format
Dostupné jsou tyto funkce:
# | Funkce | Stručný popis funkce |
---|---|---|
1 | loads | načtení objektu z formátu EDN |
2 | loads_all | načtení všech objektů z formátu EDN (výsledkem může být prázdný seznam) |
3 | dumps | opak předchozích funkcí – export dat do formátu EDN |
Nápovědu k jednotlivým funkcím opět – podobně jako v případě jazyka Clojure – získáme přímo přes interaktivní smyčku REPL.
Import příslušného balíčku:
>>> import edn_format
Zobrazení nápovědy k celému balíčku:
>>> help(edn_format) Help on package edn_format: NAME edn_format - # -*- coding: utf-8 -*- PACKAGE CONTENTS edn_dump edn_lex edn_parse exceptions immutable_dict immutable_list parsetab
Nápověda k vybrané funkci, například k funkci loads_all:
>>> help(edn_format.loads_all) ... ... ...
11. Go
Podporu formátu EDN nalezneme i v programovacím jazyce Go, pro nějž vznikla knihovna nazvaná encoding/edn, což je v ekosystému tohoto jazyka zcela konzistentní jméno, protože na „encoding/“ začínají i jména mnohých dalších knihoven zajištujících kódování či dekódování dat, popř. jejich marshaling a unmarshaling. Připomeňme si především knihovnu encoding/json.
Balíček pro práci s EDN není součástí standardní knihovny jazyka Go a proto je nutné balíček explicitně doinstalovat. To se provede následujícím příkazem:
$ go get olympos.io/encoding/edn
Po instalaci si lze prohlédnou dokumentaci s využitím go doc, popř. použít stránku s již vygenerovanou dokumentací: https://pkg.go.dev/olympos.io/encoding/edn?utm_source=godoc.
Pro převod libovolného typu (přesněji řečeno hodnoty libovolného typu) do formátu EDN se používá funkce nazvaná Marshal, kterou nalezneme v balíčku encoding/edn nainstalovaném výše zmíněným příkazem:
func Marshal(v interface{}) ([]byte, error)
Povšimněte si, že tato funkce skutečně akceptuje hodnotu libovolného typu, protože prázdné rozhraní implementuje (zcela automaticky!) každý datový typ (s tímto zajímavým konceptem „univerzálního datového typu“ se ještě několikrát setkáme, zejména v rozhraních mezi Go a dalšími systémy). Návratovou hodnotou je sekvence bajtů (nikoli řetězec!) a popř. i struktura reprezentující chybový stav, pokud k chybě skutečně došlo. V opačném případě se ve druhé návratové hodnotě funkce Marshal vrací nil, jak jsme ostatně zvyklí ze všech podobně koncipovaných funkcí, které mohou za určitých okolností skončit s chybou.
V typických zdrojových kódech se tedy setkáme s tímto idiomatickým zápisem:
edn_bytes, err := json.Marshal(a) if err != nil { log.Fatal(err) } ... ... ...
Jméno funkce odpovídá prováděné operaci, takzvanému marshalingu. Opačná operace, tj. převod dat z EDN do datových struktur Go, se nazývá unmarshalling. Jedná se o funkci s touto hlavičkou:
func Unmarshal(data []byte, v interface{}) error
Vstupem je v tomto případě pole (řez) bajtů, výstup je vrácen přes ukazatel předaný ve druhém parametru (což znamená, že se musíme sami postarat o případnou alokaci paměti pro strukturu či pro mapu). Samozřejmě, že při unmarshalingu může dojít k nějaké chybě, která je vrácena volající funkci. Pokud k chybě nedošlo, je návratová hodnota rovna nil, opět přesně podle zvyklostí programovacího jazyka Go.
12. Praktická část
Druhá část dnešního článku bude zaměřena spíše prakticky. Ukážeme si totiž některé možnosti práce s formátem EDN v programovacích jazycích Clojure, Python i Go. Uvidíme – což je ovšem očekávatelné – že se jednotlivé jazyky od sebe velmi odlišují při operacích typu serializace a deserializace datových struktur (též se setkáme s termíny marshaling a unmarshaling, které sice striktně řečeno nemají úplně shodný význam, ale mnohdy se mezi sebou zaměňují).
13. Převod mezi formátem JSON a EDN v Pythonu a Clojure
Nejprve se podívejme na způsob realizace převodů mezi formáty JSON a EDN, což může být dosti častý případ využitelný v praxi. Ukážeme si, jak se tyto převody provedou v jazyce Python a taktéž v programovacím jazyku Clojure. Začneme příkladem vypracovaným v Pythonu, který provede převod mezi souborem s daty uloženými ve formátu JSON a formátem EDN. Realizace tohoto příkladu je poměrně přímočará – nejprve se s využitím funkce load z balíčku json načte vstupní soubor a posléze se funkcí dumps z balíčku end_format data převedou do formátu EDN. Typicky se budou převádět seznamy, popř. mapy (asociativní pole):
#!/usr/bin/env python3 # Converts structured data from JSON format into EDN format. import sys import json import edn_format # Check if command line argument is specified (it is mandatory). if len(sys.argv) < 2: print("Usage:") print(" json2edn.py input_file.csv") print("Example:") print(" json2edn.py report.csv") sys.exit(1) # First command line argument should contain name of input JSON file. input_json = sys.argv[1] # Try to open the JSON file specified. with open(input_json) as json_input: # open the JSON file and parse it payload = json.load(json_input) # dump the parsed data structure into EDN format print(edn_format.dumps(payload))
Pro porovnání se podívejme na realizaci stejného převodníku, nyní ovšem vypracovaného v jazyku Clojure. Zde se použije funkce read-str z balíčku clojure.data.json pro vstup a de facto standardní funkce pprint z balíčku clojure.pprint pro výstup dat, protože ten bude kompatibilní s formátem EDN:
(ns json2edn.core) (require '[clojure.data.json :as json]) (require '[clojure.edn :as edn]) (defn json->edn "Convert JSON format into EDN format." [json-file-name edn-file-name] (let [payload (-> json-file-name slurp (json/read-str :key-fn keyword)) fout (clojure.java.io/writer edn-file-name)] (clojure.pprint/pprint payload) (clojure.pprint/pprint payload fout))) (defn -main [& args] (json->edn "details.json" "details.edn"))
Následuje příklad použití:
Vstup (reálná data):
{ "Messages": [ { "MessageId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "ReceiptHandle": "123", "MD5OfBody": "cafe0000cafe0000cafe0000cafe0000", "Body": "{\"Records\": [{\"s3\": {\"object\": {\"key\": \"7307752/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/20200609125740-0123456789abcdef0123456789abcdef\",\"size\": 11805,\"eTag\": \"beef0000beef0000beef0000beef0000\",\"sequencer\": \"005EDF87444DDF7525\"}}}]}", "Attributes": { "SenderId": "321123", "ApproximateFirstReceiveTimestamp": "1591707477622", "ApproximateReceiveCount": "1", "SentTimestamp": "1591707468964" } } ], "ResponseMetadata": { "RequestId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "date": "Tue, 09 Jun 2020 12:57:57 GMT", "content-type": "text/xml", "content-length": "22437" }, "RetryAttempts": 0 } }
Převod:
$ lein run
Výstup:
{:Messages [{:MessageId "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", :ReceiptHandle "123", :MD5OfBody "cafe0000cafe0000cafe0000cafe0000", :Body "{\"Records\": [{\"s3\": {\"object\": {\"key\": \"7307752/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/20200609125740-0123456789abcdef0123456789abcdef\",\"size\": 11805,\"eTag\": \"beef0000beef0000beef0000beef0000\",\"sequencer\": \"005EDF87444DDF7525\"}}}]}", :Attributes {:SenderId "321123", :ApproximateFirstReceiveTimestamp "1591707477622", :ApproximateReceiveCount "1", :SentTimestamp "1591707468964"}}], :ResponseMetadata {:RequestId "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", :HTTPStatusCode 200, :HTTPHeaders {:x-amzn-requestid "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", :date "Tue, 09 Jun 2020 12:57:57 GMT", :content-type "text/xml", :content-length "22437"}, :RetryAttempts 0}}
14. Převod mezi formátem EDN a JSON v Pythonu a Clojure
Opačný převod mezi formátem EDN a JSON v Pythonu není tak přímočarý, jak by se mohlo na první pohled zdát. Je tomu tak z toho důvodu, že seznamy a mapy (asociativní pole) v EDN jsou realizovány formou neměnitelné (immutable) datové struktury typu ImmutableList a ImmutableDict, kterou nelze přímo do JSONu serializovat. Je tedy nutné tyto struktury převést na běžný (měnitelný) seznam, popř. slovník, což v dalším kódu zajišťuje rekurzivně volaná funkce edn_to_map, která byla převzata z https://github.com/swaroopch/edn_format/issues/76#issuecomment-749618312:
# Taken from https://github.com/swaroopch/edn_format/issues/76#issuecomment-749618312 def edn_to_map(x): if isinstance(x, edn_format.ImmutableDict): return {edn_to_map(k): edn_to_map(v) for k, v in x.items()} elif isinstance(x, edn_format.ImmutableList): return [edn_to_map(v) for v in x] elif isinstance(x, edn_format.Keyword): return x.name else: return x
Úplný kód převodníku může vypadat takto:
#!/usr/bin/env python3 # Converts structured data from EDN format into JSON format. import sys import json import edn_format # Check if command line argument is specified (it is mandatory). if len(sys.argv) < 2: print("Usage:") print(" edn2json.py input_file.edn") print("Example:") print(" edn2json.py report.edn") sys.exit(1) # First command line argument should contain name of input EDN file. filename = sys.argv[1] # Taken from https://github.com/swaroopch/edn_format/issues/76#issuecomment-749618312 def edn_to_map(x): if isinstance(x, edn_format.ImmutableDict): return {edn_to_map(k): edn_to_map(v) for k, v in x.items()} elif isinstance(x, edn_format.ImmutableList): return [edn_to_map(v) for v in x] elif isinstance(x, edn_format.Keyword): return x.name else: return x # Try to open the EDN file specified. with open(filename, "r") as edn_input: # open the EDN file and parse it payload = edn_format.loads(edn_input.read()) print(json.dumps(edn_to_map(payload), indent=2))
Přepis do jazyka Clojure je ovšem přímočarý a struktura tohoto příkladu odpovídá příkladu pro převod opačný (viz předchozí kapitolu):
(ns edn2json.core) (require '[clojure.data.json :as json]) (require '[clojure.edn :as edn]) (defn edn->json "Convert EDN format into JSON format." [edn-file-name json-file-name] (let [payload (-> edn-file-name slurp edn/read-string) output (with-out-str (json/pprint payload))] (spit json-file-name output))) (defn -main [& args] (edn->json "details.edn" "details.json"))
Opět následuje příklad použití:
Vstup (detailní informace o projektu reprezentované v EDN):
{:aliases {"downgrade" "upgrade"}, :checkout-deps-shares [:source-paths :test-paths :resource-paths :compile-path "#'leiningen.core.classpath/checkout-deps-paths"], :clean-targets [:target-path], :compile-path "/home/ptisnovs/src/presentations/edn/edn2json/target/default/classes", :dependencies ([org.clojure/clojure "1.10.1"] [org.clojure/data.json "2.2.0"] [nrepl/nrepl "0.7.0" :exclusions ([org.clojure/clojure])] [clojure-complete/clojure-complete "0.2.5" :exclusions ([org.clojure/clojure])] [venantius/ultra "0.6.0"]), :deploy-repositories [["clojars" {:url "https://repo.clojars.org/", :password :gpg, :username :gpg}]], :description "FIXME: write description", :eval-in :subprocess, :global-vars {}, :group "edn2json", :jar-exclusions ["#\"^\\.\"" "#\"\\Q/.\\E\""], :jvm-opts ["-XX:-OmitStackTraceInFastThrow" "-XX:+TieredCompilation" "-XX:TieredStopAtLevel=1"], :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0", :url "https://www.eclipse.org/legal/epl-2.0/"}, :main edn2json.core, :monkeypatch-clojure-test false, :name "edn2json", :native-path "/home/ptisnovs/src/presentations/edn/edn2json/target/default/native", :offline? false, :pedantic? ranges, :plugin-repositories [["central" {:url "https://repo1.maven.org/maven2/", :snapshots false}] ["clojars" {:url "https://repo.clojars.org/"}]], :plugins ([lein-project-edn/lein-project-edn "0.3.0"] [venantius/ultra "0.6.0"] [lein-kibit/lein-kibit "0.1.8"]), :prep-tasks ["javac" "compile"], :profiles {:uberjar {:aot [:all], :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}, :whidbey/repl {:dependencies [[mvxcvi/whidbey "RELEASE"]], :repl-options {:init (do nil (clojure.core/require 'whidbey.repl) (whidbey.repl/init! nil)), :custom-init (do nil (whidbey.repl/update-print-fn!)), :nrepl-context {:interactive-eval {:printer whidbey.repl/render-str}}}}}, :project-edn {:output-file "details.clj"}, :release-tasks [["vcs" "assert-committed"] ["change" "version" "leiningen.release/bump-version" "release"] ["vcs" "commit"] ["vcs" "tag"] ["deploy"] ["change" "version" "leiningen.release/bump-version"] ["vcs" "commit"] ["vcs" "push"]], :repl-options {:init (do (do (clojure.core/require 'ultra.hardcore) (clojure.core/require 'whidbey.repl) (whidbey.repl/init! nil) (ultra.hardcore/configure! {:repl {:print-meta false, :map-delimiter "", :print-fallback :print, :sort-keys true}})))}, :repositories [["central" {:url "https://repo1.maven.org/maven2/", :snapshots false}] ["clojars" {:url "https://repo.clojars.org/"}]], :resource-paths ("/home/ptisnovs/src/presentations/edn/edn2json/dev-resources" "/home/ptisnovs/src/presentations/edn/edn2json/resources"), :root "/home/ptisnovs/src/presentations/edn/edn2json", :source-paths ("/home/ptisnovs/src/presentations/edn/edn2json/src"), :target-path "/home/ptisnovs/src/presentations/edn/edn2json/target/default", :test-paths ("/home/ptisnovs/src/presentations/edn/edn2json/test"), :test-selectors {:default (constantly true)}, :uberjar-exclusions ["#\"(?i)^META-INF/[^/]*\\.(SF|RSA|DSA)$\""], :url "http://example.com/FIXME", :version "0.1.0-SNAPSHOT" }
Převod:
$ lein run
Výstup:
{"description":"FIXME: write description", "compile-path": "\/home\/ptisnovs\/src\/presentations\/edn\/edn2json\/target\/default\/classes", "deploy-repositories": [["clojars", {"url":"https:\/\/repo.clojars.org\/", "password":"gpg", "username":"gpg"}]], "group":"edn2json", "license": {"name":"EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0", "url":"https:\/\/www.eclipse.org\/legal\/epl-2.0\/"}, "project-edn":{"output-file":"details.clj"}, "resource-paths": ["\/home\/ptisnovs\/src\/presentations\/edn\/edn2json\/dev-resources", "\/home\/ptisnovs\/src\/presentations\/edn\/edn2json\/resources"], "name":"edn2json", "checkout-deps-shares": ["source-paths", "test-paths", "resource-paths", "compile-path", "#'leiningen.core.classpath\/checkout-deps-paths"], "source-paths": ["\/home\/ptisnovs\/src\/presentations\/edn\/edn2json\/src"], "eval-in":"subprocess", "repositories": [["central", {"url":"https:\/\/repo1.maven.org\/maven2\/", "snapshots":false}], ["clojars", {"url":"https:\/\/repo.clojars.org\/"}]], "test-paths": ["\/home\/ptisnovs\/src\/presentations\/edn\/edn2json\/test"], "target-path": "\/home\/ptisnovs\/src\/presentations\/edn\/edn2json\/target\/default", "prep-tasks":["javac", "compile"], "native-path": "\/home\/ptisnovs\/src\/presentations\/edn\/edn2json\/target\/default\/native", "offline?":false, "root":"\/home\/ptisnovs\/src\/presentations\/edn\/edn2json", "pedantic?":"ranges", "clean-targets":["target-path"], "plugins": [["lein-project-edn", "0.3.0"], ["ultra", "0.6.0"], ["lein-kibit", "0.1.8"]], "url":"http:\/\/example.com\/FIXME", "profiles": {"uberjar": {"aot":["all"], "jvm-opts":["-Dclojure.compiler.direct-linking=true"]}, "repl": {"dependencies":[["whidbey", "RELEASE"]], "repl-options": {"init": ["do", null, ["require", "'whidbey.repl"], ["init!", null]], "custom-init":["do", null, ["update-print-fn!"]], "nrepl-context":{"interactive-eval":{"printer":"render-str"}}}}}, "plugin-repositories": [["central", {"url":"https:\/\/repo1.maven.org\/maven2\/", "snapshots":false}], ["clojars", {"url":"https:\/\/repo.clojars.org\/"}]], "aliases":{"downgrade":"upgrade"}, "version":"0.1.0-SNAPSHOT", "jar-exclusions":["#\"^\\.\"", "#\"\\Q\/.\\E\""], "main":"edn2json.core", "global-vars":{}, "uberjar-exclusions":["#\"(?i)^META-INF\/[^\/]*\\.(SF|RSA|DSA)$\""], "jvm-opts": ["-XX:-OmitStackTraceInFastThrow", "-XX:+TieredCompilation", "-XX:TieredStopAtLevel=1"], "dependencies": [["clojure", "1.10.1"], ["data.json", "2.2.0"], ["nrepl", "0.7.0", "exclusions", [["clojure"]]], ["clojure-complete", "0.2.5", "exclusions", [["clojure"]]], ["ultra", "0.6.0"]], "release-tasks": [["vcs", "assert-committed"], ["change", "version", "leiningen.release\/bump-version", "release"], ["vcs", "commit"], ["vcs", "tag"], ["deploy"], ["change", "version", "leiningen.release\/bump-version"], ["vcs", "commit"], ["vcs", "push"]], "repl-options": {"init": ["do", ["do", ["require", "'ultra.hardcore"], ["require", "'whidbey.repl"], ["init!", null], ["configure!", {"repl": {"print-meta":false, "map-delimiter":"", "print-fallback":"print", "sort-keys":true}}]]]}, "test-selectors":{"default":["constantly", true]}, "monkeypatch-clojure-test":false}
15. Převod mezi souborem .properties a formátem EDN v Clojure
Relativně často se v aplikacích vytvářených v jazyku Clojure setkáme se soubory s koncovkou .properties, které jsou ostatně v ekosystému programovacího jazyka Java běžné. Převod takových souborů do EDN je snadný, protože můžeme využít možnosti nabízené třídou java.util.Properties pro načtení hodnot s jejich následnou konverzí funkcí properties->map:
(ns properties2edn.core) (defn properties->map "Convert properties entries into a map. Keys are converted into proper keywords." [properties] (into {} (for [[k v] properties] [(keyword k) v]))) (defn load-properties-file "Load configuration from the provided properties file." [file-name] (with-open [reader (clojure.java.io/reader file-name)] (let [properties (java.util.Properties.)] (.load properties reader) (properties->map properties)))) (defn properties->edn "Convert properties file into EDN format." [properties-file-name edn-file-name] (let [payload (load-properties-file properties-file-name) fout (clojure.java.io/writer edn-file-name)] (clojure.pprint/pprint payload fout))) (defn -main [& args] (properties->edn "test.properties" "test.edn"))
Příklad převodu:
Vstup (soubor s reálnými daty):
# This file is shared currently between this Gradle build and the # Ant builds for fd303 and JavaScript. Keep the dotted notation for # the properties to minimize the changes in the dependencies. thrift.version=0.14.0 thrift.groupid=org.apache.thrift release=false # Local Install paths install.path=/usr/local/lib install.javadoc.path=/usr/local/lib # Test execution properties testPort=9090 # Test with Clover Code coverage (disabled by default) cloverEnabled=false # Maven dependency download locations mvn.repo=https://repo1.maven.org/maven2 apache.repo=https://repository.apache.org/content/repositories/releases # Apache Maven publish license=https://www.apache.org/licenses/LICENSE-2.0.txt maven-repository-url=https://repository.apache.org/service/local/staging/deploy/maven2 maven-repository-id=apache.releases.https # Dependency versions httpclient.version=4.5.10 httpcore.version=4.4.12 slf4j.version=1.7.28 #servlet.version=2.5 #It contains servlet3 tomcat.embed.version=8.5.46 junit.version=4.12 mockito.version=1.10.19 javax.annotation.version=1.3.2
Převod:
$ lein run
Výstup:
{:tomcat.embed.version "8.5.46", :thrift.version "0.14.0", :httpclient.version "4.5.10", :mockito.version "1.10.19", :javax.annotation.version "1.3.2", :thrift.groupid "org.apache.thrift", :license "https://www.apache.org/licenses/LICENSE-2.0.txt", :install.javadoc.path "/usr/local/lib", :slf4j.version "1.7.28", :httpcore.version "4.4.12", :release "false", :mvn.repo "https://repo1.maven.org/maven2", :maven-repository-url "https://repository.apache.org/service/local/staging/deploy/maven2", :maven-repository-id "apache.releases.https", :testPort "9090", :apache.repo "https://repository.apache.org/content/repositories/releases", :junit.version "4.12", :cloverEnabled "false", :install.path "/usr/local/lib"}
16. Převod mezi XML a EDN (plná varianta zachovávající vlastnosti XML)
V základní knihovně programovacího jazyka Clojure nalezneme i balíček clojure.xml s funkcemi určenými pro načítání souborů XML. Výsledkem načtení (parsingu) je mapa, jejíž prvky mají jednotný formát. Samotný převodník z formátu XML přes mapu do formátu EDN je přímočarý (další možnosti jsou ukázány v navazující kapitole):
(ns xml2edn.core) (use '[clojure.xml]) (defn xml->edn "Convert XML file into EDN format." [xml-file-name edn-file-name] (let [payload (clojure.xml/parse "nested.xml") fout (clojure.java.io/writer edn-file-name)] (clojure.pprint/pprint payload fout))) (defn -main [& args] (xml->edn "nested.xml" "nested.edn"))
Podívejme se nyní, jakým způsobem je zkonvertován následující (na první pohled velmi jednoduchý) soubor:
<?xml version="1.0"?> <first> <second value="A"> <third> <fourth>Hello A</fourth> </third> </second> <second value="B"> <third> <fourth>Hello B</fourth> </third> </second> </first>
Výsledkem převodu bude relativně složitá rekurzivní mapa, která ovšem přesně odpovídá vstupnímu XML, ovšem s tím rozdílem, že jsou explicitně vypsány i hodnoty atributů:
{:tag :first, :attrs nil, :content [{:tag :second, :attrs {:value "A"}, :content [{:tag :third, :attrs nil, :content [{:tag :fourth, :attrs nil, :content ["Hello A"]}]}]} {:tag :second, :attrs {:value "B"}, :content [{:tag :third, :attrs nil, :content [{:tag :fourth, :attrs nil, :content ["Hello B"]}]}]}]}
17. Převod XML do různých forem formátu EDN s využitím knihovny Tupelo.forest
Do XML je možné provést převod hodnot (tedy vektorů, map atd.) několika různými způsoby. Většina z nich není podporována v základních knihovnách jazyka Clojure, takže si budeme muset vypomoci externí knihovnou, konkrétně knihovnou nazvanou Tupelo.forest. Do projektového souboru se tato závislost přidá snadno:
(defproject forest-demo "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.1"] [tupelo "21.04.13"]] :main ^:skip-aot forest-demo.core :target-path "target/%s" :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
V dalším demonstračním příkladu použijeme několik způsobů převodu a serializace hodnot z XML do EDN. Jednotlivé varianty EDN jsou nazvány „enlive“ „hiccup“, „bush“ a „tree“ (jsou pro to různé důvody, například první název vychází ze jména šablonovacího systému Enlive):
(ns forest-demo.core) (require '[clojure.xml :as xml]) (require '[tupelo.forest :as tf]) (defn pprint-to-file [filename payload] (clojure.pprint/pprint payload (clojure.java.io/writer filename))) (defn -main [& args] (let [payload (xml/parse "nested.xml") hiccup-format (tf/enlive->hiccup payload) bush-format (tf/enlive->bush payload) tree-format (tf/enlive->tree payload)] (pprint-to-file "nested-enlive.edn" payload) (pprint-to-file "nested-hiccup.edn" hiccup-format) (pprint-to-file "nested-bush.edn" bush-format) (pprint-to-file "nested-tree.edn" tree-format)))
Vstupem do příkladu je následující XML soubor:
<?xml version="1.0"?> <first> <second value="A"> <third> <fourth>Hello A</fourth> </third> </second> <second value="B"> <third> <fourth>Hello B</fourth> </third> </second> </first>
Export do formátu „enlive“:
{:tag :first, :attrs nil, :content [{:tag :second, :attrs {:value "A"}, :content [{:tag :third, :attrs nil, :content [{:tag :fourth, :attrs nil, :content ["Hello A"]}]}]} {:tag :second, :attrs {:value "B"}, :content [{:tag :third, :attrs nil, :content [{:tag :fourth, :attrs nil, :content ["Hello B"]}]}]}]}
Export do formátu „hiccup“ je nejstručnější:
[:first [:second {:value "A"} [:third [:fourth "Hello A"]]] [:second {:value "B"} [:third [:fourth "Hello B"]]]]
Formát nazvaný „bush“:
[{:tag :first} [{:value "A", :tag :second} [{:tag :third} [{:tag :fourth, :value "Hello A"}]]] [{:value "B", :tag :second} [{:tag :third} [{:tag :fourth, :value "Hello B"}]]]]
A konečně formát nazvaný „tree“, v němž jsou synovské uzly reprezentovány formou vloženého vektoru:
{:tag :first, :tupelo.forest/kids [{:value "A", :tag :second, :tupelo.forest/kids [{:tag :third, :tupelo.forest/kids [{:tag :fourth, :value "Hello A", :tupelo.forest/kids []}]}]} {:value "B", :tag :second, :tupelo.forest/kids [{:tag :third, :tupelo.forest/kids [{:tag :fourth, :value "Hello B", :tupelo.forest/kids []}]}]}]}
Ve skutečnosti jsou možnosti knihovny Tupelo.forest ještě mnohem větší a pravděpodobně se s nimi setkáme v některém dalším článku o programovacím jazyku Clojure.
18. Práce s formátem EDN v jazyce Go, vlastní štítky rozšiřující možnosti EDN
V navazujícím článku téma formátu EDN dokončíme. Ukážeme si především použití formátu EDN v programovacím jazyce Go (ostatně některé demonstrační příklady jsou již uloženy do repositáře) a taktéž se budeme zabývat problematikou tvorby vlastních štítků. Jedná se o užitečný koncept, protože právě štítky umožňují rozšiřování EDN o další datové typy, což je vlastnost, kterou v dalších podobně koncipovaných formátech většinou nenalezneme.
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes popsaných demonstračních příkladů vyvinutých v programovacích jazycích Clojure, Python i Go, byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/presentations:
# | Zdrojový kód | Stručný popis souboru | Cesta |
---|---|---|---|
1 | json2edn.py | konverze mezi formátem JSON a EDN naprogramovaná v Pythonu | https://github.com/tisnik/presentations/blob/master/edn/json2edn.py |
2 | edn2json.py | konverze mezi formátem EDN a JSON naprogramovaná v Pythonu | https://github.com/tisnik/presentations/blob/master/edn/edn2json.py |
3 | json2edn (adresář) | konverze mezi formátem JSON a EDN naprogramovaná v Clojure | https://github.com/tisnik/presentations/blob/master/edn/json2edn |
4 | edn2json (adresář) | konverze mezi formátem EDN a JSON naprogramovaná v Clojure | https://github.com/tisnik/presentations/blob/master/edn/edn2json |
3 | properties2edn (adresář) | konverze mezi .properties souborem a formátem EDN (Clojure) | https://github.com/tisnik/presentations/blob/master/edn/properties2edn |
4 | xml2edn (adresář) | konverze mezi XML a formátem EDN (Clojure, plná konverze) | https://github.com/tisnik/presentations/blob/master/edn/xml2edn |
5 | forest-demo (adresář) | různé možnosti konverze mezi XML a formátem EDN | https://github.com/tisnik/presentations/blob/master/edn/forest-demo |
6 | go-edn-1 (adresář) | serializace (marshalling) datové struktury do formátu EDN | https://github.com/tisnik/presentations/blob/master/edn/go-edn-1 |
7 | go-edn-2 (adresář) | specifikace názvů klíčů v EDN formátu | https://github.com/tisnik/presentations/blob/master/edn/go-edn-2 |
8 | go-edn-3 (adresář) | uložení pole datových struktur do formátu EDN | https://github.com/tisnik/presentations/blob/master/edn/go-edn-3 |
9 | go-edn-4 (adresář) | uložení mapy datových struktur do formátu EDN | https://github.com/tisnik/presentations/blob/master/edn/go-edn-4 |
10 | go-edn-5 (adresář) | deserializace (unmarshalling) datové struktury z formátu EDN | https://github.com/tisnik/presentations/blob/master/edn/go-edn-5 |
20. Odkazy na Internetu
- edn – extensible data notation
https://github.com/edn-format/edn - Programming with Data and EDN
https://docs.datomic.com/cloud/whatis/edn.html - Video about EDN
https://docs.datomic.com/cloud/livetutorial/edntutorial.html - (Same) video about EDN on Youtube
https://www.youtube.com/watch?v=5eKgRcvEJxU - clojure.edn
https://clojuredocs.org/clojure.edn - API for clojure.edn – Clojure v1.10.2 (stable)
https://clojure.github.io/clojure/clojure.edn-api.html - Clojure EDN Walkthrough
https://www.compoundtheory.com/clojure-edn-walkthrough/ - Články týkající se Pythonu na Rootu
https://www.root.cz/n/python/ - Články týkající se programovacího jazyka Clojure na Rootu
https://www.root.cz/n/clojure/ - Seriál Programovací jazyk Go
https://www.root.cz/serialy/programovaci-jazyk-go/ - Crux
https://opencrux.com/main/index.html - Crux Installation
https://opencrux.com/reference/21.04–1.16.0/installation.html - read
https://clojuredocs.org/clojure.edn/read - read-string
https://clojuredocs.org/clojure.edn/read-string - Tupelo 21.04.12 (dokumentace)
https://cloojure.github.io/doc/tupelo/ - tupelo – Clojure With A Spoonful of Honey
https://clojars.org/tupelo - Clojure Cookbook: Templating HTML with Enlive
https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc - An Introduction to Enlive
https://github.com/swannodette/enlive-tutorial/ - Enlive na GitHubu
https://github.com/cgrand/enlive - data.json
https://github.com/clojure/data.json - data.json API reference
https://clojure.github.io/data.json/ - Clojure: Writing JSON to a File/Reading JSON From a File
https://dzone.com/articles/clojure-writing-json - How to pretty print JSON to a file in Clojure?
https://stackoverflow.com/questions/23307552/how-to-pretty-print-json-to-a-file-in-clojure - go-edn / edn
https://github.com/go-edn/edn - Queries (Crux)
https://opencrux.com/reference/21.04–1.16.0/queries.html - Essential EDN
https://opencrux.com/tutorials/essential-edn.html - Babashka: interpret Clojure určený pro rychlé spouštění utilit z příkazového řádku
https://www.root.cz/clanky/babashka-interpret-clojure-urceny-pro-rychle-spousteni-utilit-z-prikazoveho-radku/ - Introducing JSON
https://www.json.org/json-en.html - ISO 8601
https://xkcd.com/1179/ - What is the right JSON date format
https://stackoverflow.com/questions/10286204/what-is-the-right-json-date-format - ClojureScript REPL
https://clojurescript.io/