Formát EDN: extensible data notation (dokončení)

22. 4. 2021
Doba čtení: 30 minut

Sdílet

 Autor: Clojure
Ve druhém článku o formátu EDN si ukážeme vybrané příklady použití tohoto datového formátu v jazyce Go, některé specifické vlastnosti práce s EDN v Pythonu a taktéž se budeme zabývat problematikou tvorby vlastních štítků (tags).

Obsah

1. Formát EDN: extensible data notation (dokončení)

2. Serializace hodnot základních datových typů Go do formátu EDN

3. Převod celočíselných hodnot do formátu EDN

4. Chování systému při pokusu o převod hodnot, které nemají ve formátu EDN podporu

5. Hodnoty nil a řetězce převáděné do formátu EDN

6. Převod polí a řezů do formátu EDN

7. Struktury (záznamy) a jejich přímý převod do formátu EDN

8. Změna názvů klíčů ve výsledném souboru EDN

9. Pole záznamů a jejich převod do formátu EDN

10. Jedno z nejčastějších použití: mapy struktur (záznamů)

11. Import dat ve formátu EDN do aplikace psané v Go

12. Import jednoduché struktury (záznamu) se známým obsahem, import polí

13. Načtení předem neznámé struktury ze souboru ve formátu EDN

14. Problematika načítání EDN v jazycích se sémantikou odlišnou od Clojure

15. Konverze hodnot prováděné v Pythonu

16. Načtení složitějších datových struktur

17. Uživatelem definované štítky (tagy)

18. Práce s uživatelskými štítky v Pythonu

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Formát EDN: extensible data notation (dokončení)

V dnešním článku, který navazuje na úterní článek, dokončíme popis použití formátu EDN neboli Extensible Data Notation. Ukážeme si především vybrané příklady použití formátu EDN v programovacím jazyce Go, některé specifické vlastnosti práce s EDN v Pythonu (což souvisí s typovým systémem tohoto jazyka) a taktéž se budeme zabývat problematikou tvorby vlastních štítků (tags). 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. Zmíníme se ovšem i o dalších specifických vlastnostech, například práci se jmennými prostory atd.

Pro začátek si připomeňme, že 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ů. A právě možnost specifikovat množiny, společně s prakticky neomezenými možnosti při použití klíčů v mapách, dělají z EDN dosti flexibilní formát s užitečnou sémentikou (to, že se přenáší množina a nikoli seznam či pole, je zřejmé již do prvního znaku atd.).

2. Serializace hodnot základních datových typů Go do formátu EDN

Pro převod libovolného typu (přesněji řečeno hodnoty libovolného typu) do formátu EDN se v programovacím jazyku Go používá funkce nazvaná Marshal, kterou nalezneme v balíčku encoding/edn (ten ovšem musí být explicitně nainstalován, neboť není součástí standardní knihovny Go):

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.

Poznámka: to, že se při marshalingu vytváří sekvence bajtů a nikoli řetězec, není vážný problém, neboť převod na řetězec je triviální:
byteStream, err := edn.Marshal(valueOfAnyType)
 
if err != nil {
        fmt.Println(string(byteStream))
}

3. Převod celočíselných hodnot do formátu EDN

V dnešním prvním demonstračním příkladu, jehož zdrojový kód naleznete na adrese https://github.com/tisnik/pre­sentations/tree/master/ed­n/go-edn-basic-types-1 je ukázán způsob konverze celých čísel se znaménkem do formátu EDN. Připomeňme si, že v Go existuje několik celočíselných typů se znaménkem, přičemž některé typy jsou pouze jmennými aliasy:

# Označení Rozsah hodnot Stručný popis
1 int8 –128 až 127 osmibitové celé číslo se znaménkem
2 int16 –32768 až 32767 16bitové celé číslo se znaménkem
3 int32 –2147483648 až 2147483647 32bitové celé číslo se znaménkem
4 int64 –9223372036854775808 až 9223372036854775807 64bitové celé číslo se znaménkem
       
5 int různý odpovídá buď typu int32 nebo int64
       
6 rune –2147483648 až 2147483647 alias pro typ int32, používá se pro znaky

Převod hodnot všech skupin celočíselných hodnot se znaménkem může být proveden následovně (vynechali jsme pouze typ int, který je aliasem pro int32 nebo int64, a to podle konkrétní použité architektury):

package main
 
import (
        "fmt"
        "olympos.io/encoding/edn"
)
 
func main() {
        var a int8 = -10
        var b int16 = -1000
        var c int32 = -10000
        var d int32 = -1000000
 
        var r1 rune = 'a'
        var r2 rune = '\x40'
        var r3 rune = '\n'
        var r4 rune = '\u03BB'
 
        aEDN, err := edn.Marshal(a)
        fmt.Println(string(aEDN))
        fmt.Println(err)
 
        bEDN, err := edn.Marshal(b)
        fmt.Println(string(bEDN))
        fmt.Println(err)
 
        cEDN, err := edn.Marshal(c)
        fmt.Println(string(cEDN))
        fmt.Println(err)
 
        dEDN, err := edn.Marshal(d)
        fmt.Println(string(dEDN))
        fmt.Println(err)
 
        fmt.Println()
 
        r1EDN, err := edn.Marshal(r1)
        fmt.Println(string(r1EDN))
        fmt.Println(err)
 
        r2EDN, err := edn.Marshal(r2)
        fmt.Println(string(r2EDN))
        fmt.Println(err)
 
        r3EDN, err := edn.Marshal(r3)
        fmt.Println(string(r3EDN))
        fmt.Println(err)
 
        r4EDN, err := edn.Marshal(r4)
        fmt.Println(string(r4EDN))
        fmt.Println(err)
}

Po spuštění demonstračního příkladu by se mělo zobrazit několik textových řádků, z nichž každý reprezentuje validní EDN (a pod ním hodnotu nil značící, že nedošlo k žádné chybě):

-10
<nil>
-1000
<nil>
-10000
<nil>
-1000000
<nil>
 
97
<nil>
64
<nil>
10
<nil>
955
<nil>

Druhý demonstrační příklad, který najdete na adrese https://github.com/tisnik/pre­sentations/tree/master/ed­n/go-edn-basic-types-2, provádí tytéž operace, ovšem nad celočíselnými hodnotami bez znaménka:

# Označení Rozsah hodnot Stručný popis
1 uint8 0 až 255 osmibitové celé číslo bez znaménka
2 uint16 0 až 65535 16bitové celé číslo bez znaménka
3 uint32 0 až 4294967295 32bitové celé číslo bez znaménka
4 uint64 0 až 18446744073709551615 64bitové celé číslo bez znaménka
       
5 uint různý odpovídá buď typu uint32 nebo uint64
       
6 byte 0 až 255 alias pro typ uint8

Zdrojový kód tohoto demonstračního příkladu vypadá takto:

package main
 
import (
        "fmt"
        "olympos.io/encoding/edn"
)
 
func main() {
        var b8 byte = 0x42
        var a uint8 = 10
        var b uint16 = 1000
        var c uint32 = 10000
        var d uint32 = 1000000
 
        b8EDN, err := edn.Marshal(b8)
        fmt.Println(string(b8EDN))
        fmt.Println(err)
 
        aEDN, err := edn.Marshal(a)
        fmt.Println(string(aEDN))
        fmt.Println(err)
 
        bEDN, err := edn.Marshal(b)
        fmt.Println(string(bEDN))
        fmt.Println(err)
 
        cEDN, err := edn.Marshal(c)
        fmt.Println(string(cEDN))
        fmt.Println(err)
 
        dEDN, err := edn.Marshal(d)
        fmt.Println(string(dEDN))
        fmt.Println(err)
}

Po spuštění tohoto demonstračního příkladu získáme deset řádků – pět korektních EDN + informaci o tom, že nedošlo k žádné chybě:

66
<nil>
10
<nil>
1000
<nil>
10000
<nil>
1000000
<nil>
Poznámka: ve skutečnosti mohou (ale nemusí) nastat problémy s datovými typy int64, uint64, popř. některými hodnotami typu uint32 (těmi, které se nevejdou do rozsahu int32). Tyto problémy nenastanou na straně Go, ale knihoven jiných programovacích jazyků.

4. Chování systému při pokusu o převod hodnot, které nemají ve formátu EDN podporu

Hodnoty některých datových typů programovacího jazyka Go ovšem nemají ve formátu EDN přímý ekvivalent a proto nejsou převoditelné (alespoň ne automaticky). Týká se to například i datových typů complex64 a complex128, kterými jsou v jazyku Go reprezentována komplexní čísla (dvojice hodnot typu float32, popř. float64):

var a complex64 = -1.5 + 0i
var b complex64 = 1.5 + 1000i
var c complex64 = 1e30 + 1e30i
var d complex64 = 1i
Poznámka: zajímavé je, že Clojure neobsahuje podporu pro komplexní čísla, zatímco jazyk Kawa ano.

Převod (s kontrolou průběhu převodu) je implementován v dnešním třetím demonstračním příkladu s tímto zdrojovým kódem:

package main
 
import (
        "fmt"
        "olympos.io/encoding/edn"
)
 
func main() {
        var a complex64 = -1.5 + 0i
        var b complex64 = 1.5 + 1000i
        var c complex64 = 1e30 + 1e30i
        var d complex64 = 1i
 
        aEDN, err := edn.Marshal(a)
        fmt.Println(string(aEDN))
        fmt.Println(err)
 
        bEDN, err := edn.Marshal(b)
        fmt.Println(string(bEDN))
        fmt.Println(err)
 
        cEDN, err := edn.Marshal(c)
        fmt.Println(string(cEDN))
        fmt.Println(err)
 
        dEDN, err := edn.Marshal(d)
        fmt.Println(string(dEDN))
        fmt.Println(err)
}

Po spuštění takto připraveného demonstračního příkladu je již jasné, že se konverze nezadařila – po každém prázdném řádku (výsledek nepovedené konverze) je totiž vypsána i chyba, ke které došlo:

 
edn: unsupported type: complex64
 
edn: unsupported type: complex64
 
edn: unsupported type: complex64
 
edn: unsupported type: complex64
Poznámka: v případě, že kontrolu chyby neprovedete, vrátí se pouze prázdný řetězec (resp. prázdný řez bajtů), což je pochopitelně nekorektní chování – tuto chybu je zapotřebí ošetřit.

5. Hodnoty nil a řetězce převáděné do formátu EDN

Podívejme se nyní na způsob uložení hodnot nil do formátu EDN. Připomeňme si, že s hodnotou nil je v jazyce Go spojen i typ této hodnoty. Podrobnosti jsme si řekli v článku Problematika nulových hodnot v Go, aneb proč nil != nil, v němž jsme si uvedli, že nil může reprezentovat „nulovou hodnotu“ pro některé datové typy:

# Datový typ Nulová hodnota
1 bool false
2 int (a varianty) 0
3 float (obě varianty) 0.0
4 complex (obě varianty) 0.0+0.0i
5 string ""
6 pointer nil
7 slice nil
8 map nil
9 channel nil
10 function nil
11 interface nil
12 struct prvky s nulovými hodnotami

Na druhou stranu ve formátu EDN je typ spojen přímo s hodnotou, přesněji řečeno literál představující hodnotu současně určuje i typ, takže nil zůstane beztypové. Po úplnost je do příkladu přidána i serializace běžného řetězce:

package main
 
import (
        "fmt"
        "olympos.io/encoding/edn"
)
 
func main() {
        var a interface{} = nil
        var b map[string]string = nil
        c := "foo bar baz"
 
        aEDN, err := edn.Marshal(a)
        fmt.Println(string(aEDN))
        fmt.Println(err)
 
        fmt.Println()
 
        bEDN, err := edn.Marshal(b)
        fmt.Println(string(bEDN))
        fmt.Println(err)
 
        fmt.Println()
 
        cEDN, err := edn.Marshal(c)
        fmt.Println(string(cEDN))
        fmt.Println(err)
}

Výsledek běhu tohoto demonstračního příkladu opět ukazuje jak serializované hodnoty, tak i fakt, že při serializaci nedošlo k chybě:

nil
<nil>
 
nil
<nil>
 
"foo bar baz"
<nil>

6. Převod polí a řezů do formátu EDN

S EDN obsahujícími pouze jedinou skalární hodnotu se nesetkáme příliš často, proto se nyní podívejme na způsob práce se strukturovanými daty. Začneme s poli (arrays), popř. s řezy, které se do formátu EDN provádí naprosto stejným způsobem jako pole. Vyzkoušíme si převod polí, v nichž jsou typy prvků shodné:

var a1 [10]byte
var a2 [10]int32
a3 := [10]int32{1, 10, 2, 9, 3, 8, 4, 7, 5, 6}
a4 := []string{"www", "root", "cz"}

Převádět samozřejmě můžeme i dvourozměrné (popř. i vícerozměrné) pole:

matice := [4][3]float32{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
        {0, -1, 0},
}

V případě, že je nutné pracovat s poli, v nichž mohou mít prvky rozdílné typy hodnot (což je v EDN relativně častý požadavek), můžeme si v programovacím jazyce Go pomoci poli s prvky typu „prázdné rozhraní“ (přesněji se všemi typy, které prázdné rozhraní implementují – to jsou všechny typy):

a5 := []interface{}{1, "root", 3.1415, true, []int{1, 2, 3, 4}}

Následuje úplný zdrojový kód dnešního pátého demonstračního příkladu, v němž k marshalingu polí (s prvky různých typů) dochází:

package main
 
import (
        "fmt"
        "olympos.io/encoding/edn"
)
 
func main() {
        var a1 [10]byte
        var a2 [10]int32
        a3 := [10]int32{1, 10, 2, 9, 3, 8, 4, 7, 5, 6}
        a4 := []string{"www", "root", "cz"}
        a5 := []interface{}{1, "root", 3.1415, true, []int{1, 2, 3, 4}}
 
        matice := [4][3]float32{
                {1, 2, 3},
                {4, 5, 6},
                {7, 8, 9},
                {0, -1, 0},
        }
 
        a1EDN, err := edn.MarshalPPrint(a1, nil)
        fmt.Println(string(a1EDN))
        fmt.Println(err)
        fmt.Println()
 
        a2EDN, err := edn.MarshalPPrint(a2, nil)
        fmt.Println(string(a2EDN))
        fmt.Println(err)
        fmt.Println()
 
        a3EDN, err := edn.MarshalPPrint(a3, nil)
        fmt.Println(string(a3EDN))
        fmt.Println(err)
        fmt.Println()
 
        a4EDN, err := edn.MarshalPPrint(a4, nil)
        fmt.Println(string(a4EDN))
        fmt.Println(err)
        fmt.Println()
 
        a5EDN, err := edn.MarshalPPrint(a5, nil)
        fmt.Println(string(a5EDN))
        fmt.Println(err)
        fmt.Println()
 
        maticeEDN, err := edn.MarshalPPrint(matice, nil)
        fmt.Println(string(maticeEDN))
        fmt.Println(err)
}
Poznámka: povšimněte si, že nyní pro tisk používáme funkci pojmenovanou MarshalPPrint, která používá bílé znaky pro vygenerování naformátovaného EDN.

První tři pole se převedou do formátu JSON takto:

[0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
[1 10 2 9 3 8 4 7 5 6]

Další pole obsahuje řetězce, které mohou vypadat následovně:

["www" "root" "cz"]

Následuje pole prvků různých typů (resp. hodnot implementujících prázdné rozhraní):

[1 "root" 3.1415 true [1 2 3 4]]

A konečně posledním případem je dvourozměrné pole hodnot typu float32, které sice nejsou v EDN přímo podporovány, ale je proveden jejich převod na float64/double (což je převod, v němž nedochází ke ztrátě přesnosti ani rozsahu). Výsledek bude následující (jedná se o vektor vektorů):

[[1.0 2.0 3.0]
 [4.0 5.0 6.0]
 [7.0 8.0 9.0]
 [0.0 -1.0 0.0]]

7. Struktury (záznamy) a jejich přímý převod do formátu EDN

Do formátu EDN pochopitelně můžeme převádět i jednotlivé struktury. Struktura se zkonvertuje do EDN ve formě mapy (neboli asociativního pole). Ovšem musíme si dát pozor na to, že převedeny budou jen ty položky záznamu, jejichž jméno začíná velkým písmenem. Ostatně se o tom můžeme snadno přesvědčit při překladu a spuštění dalšího demonstračního příkladu:

package main
 
import (
        "fmt"
        "olympos.io/encoding/edn"
)
 
// User1 je uživatelsky definovaná datová struktura s privátními atributy
type User1 struct {
        id      uint32
        name    string
        surname string
}
 
// User2 je uživatelsky definovaná datová struktura s viditelnými atributy
type User2 struct {
        ID      uint32
        Name    string
        Surname string
}
 
func main() {
        user1 := User1{
                1,
                "Pepek",
                "Vyskoč"}
 
        user2 := User2{
                1,
                "Pepek",
                "Vyskoč"}
 
        fmt.Println("user1")
        user1EDN, err := edn.Marshal(user1)
        fmt.Println(string(user1EDN))
        fmt.Println(err)
        fmt.Println()
 
        user1PrettyEDN, err := edn.MarshalPPrint(user1, nil)
        fmt.Println(string(user1PrettyEDN))
        fmt.Println(err)
        fmt.Println()
 
        fmt.Println("user2")
        user2EDN, err := edn.Marshal(user2)
        fmt.Println(string(user2EDN))
        fmt.Println(err)
        fmt.Println()
 
        user2PrettyEDN, err := edn.MarshalPPrint(user2, nil)
        fmt.Println(string(user2PrettyEDN))
        fmt.Println(err)
        fmt.Println()
}

První struktura se převede na prázdný objekt (má položky pojmenované malými písmeny), druhá se již převede korektně:

{}
{:iD 1 :name"Pepek":surname"Vyskoč"}

V příkladu je taktéž ukázán přehlednější „pretty-printovaný“ výstup:

{:iD 1,
 :name "Pepek",
 :surname "Vyskoč"}
Poznámka: v dalším textu si ukážeme, jak lze toto omezení částečně obejít s využitím tzv. struktur se značkami.

8. Změna názvů klíčů ve výsledném souboru EDN

Velmi často se ovšem setkáme s požadavkem na to, aby měly položky v souboru EDN odlišné označení – ostatně pojmenování položek stylem CamelCase není ve světě EDN příliš běžné. Řešení tohoto problému existuje, i když není příliš elegantní – v Go jsou totiž podporovány takzvané „tagged structs“, což jsou běžné struktury/záznamy, za jejichž položkami jsou v řetězci zapsaném ve zpětných apostrofech uvedeny příslušné názvy, které se mají v EDN objevit:

type User3 struct {
        ID      uint32 `edn:"id"`
        Name    string `edn:"user-name"`
        Surname string `edn:"surname"`
}

Nevýhodou tohoto způsobu deklarace je fakt, že obsah řetězců není překladačem nijak kontrolován, takže se o případných problémech (chybějící uvozovky atd.) dozvíme až v čase běhu aplikace (ideální z testů).

Chování si můžeme ověřit na tomto příkladu:

package main
 
import (
        "fmt"
        "olympos.io/encoding/edn"
)
 
// User3 je uživatelsky definovaná datová struktura s viditelnými atributy
type User3 struct {
        ID      uint32 `edn:"id"`
        Name    string `edn:"user-name"`
        Surname string `edn:"surname"`
}
 
func main() {
        user3 := User3{
                1,
                "Pepek",
                "Vyskoč"}
 
        fmt.Println("user3")
        user3EDN, err := edn.Marshal(user3)
        fmt.Println(string(user3EDN))
        fmt.Println(err)
        fmt.Println()
 
        user3PrettyEDN, err := edn.MarshalPPrint(user3, nil)
        fmt.Println(string(user3PrettyEDN))
        fmt.Println(err)
        fmt.Println()
}

S následujícím výsledkem (jsou v něm zobrazeny i případné chyby, které by mohly při převodu nastat):

user3
{:id 1 :user-name"Pepek":surname"Vyskoč"}
<nil>
 
{:id 1,
 :user-name "Pepek",
 :surname "Vyskoč"}
<nil>

9. Pole záznamů a jejich převod do formátu EDN

Často se taktéž setkáme s poli nebo řezy, jejichž prvky jsou struktury. Převod takové hierarchické datové struktury do formátu je stejně přímočarý, jako v předchozím příkladu:

package main
 
import (
        "fmt"
        "olympos.io/encoding/edn"
)
 
// User3 je uživatelsky definovaná datová struktura s viditelnými atributy
type User3 struct {
        ID      uint32 `edn:"id"`
        Name    string `edn:"user-name"`
        Surname string `edn:"surname"`
}
 
func main() {
        var users = [3]User3{
                User3{
                        ID:      1,
                        Name:    "Pepek",
                        Surname: "Vyskoč"},
                User3{
                        ID:      2,
                        Name:    "Pepek",
                        Surname: "Vyskoč"},
                User3{
                        ID:      3,
                        Name:    "Josef",
                        Surname: "Vyskočil"},
        }
 
        fmt.Println("users")
        usersEDN, err := edn.Marshal(users)
        fmt.Println(string(usersEDN))
        fmt.Println(err)
        fmt.Println()
 
        usersPrettyEDN, err := edn.MarshalPPrint(users, nil)
        fmt.Println(string(usersPrettyEDN))
        fmt.Println(err)
        fmt.Println()
}

Nenaformátovaný výsledek:

[{:id 1 :user-name"Pepek":surname"Vyskoč"}{:id 2 :user-name"Pepek":surname"Vyskoč"}{:id 3 :user-name"Josef":surname"Vyskočil"}]

Naformátovaný výsledek:

[{:id 1,
  :user-name "Pepek",
  :surname "Vyskoč"}
 {:id 2,
  :user-name "Pepek",
  :surname "Vyskoč"}
 {:id 3,
  :user-name "Josef",
  :surname "Vyskočil"}]

10. Jedno z nejčastějších použití: mapy struktur (záznamů)

Pravděpodobně nejčastěji se při práci s formátem EDN setkáme s mapami (resp. asociativními poli), jejichž hodnotami jsou záznamy. Mapy mají v JSON jedno omezené – klíči mohou být řetězce. Toto omezení v programovacím jazyce Go neplatí a neplatí ani pro formát EDN.

V dalším demonstračním příkladu je ukázán převod mapy se dvěma dvojicemi klíč-hodnota. Klíče jsou typu string, hodnotami jsou struktury/záznamy s předem známými prvky:

package main
 
import (
        "fmt"
        "olympos.io/encoding/edn"
)
 
// User3 je uživatelsky definovaná datová struktura s viditelnými atributy
type User3 struct {
        ID      uint32 `edn:"id"`
        Name    string `edn:"user-name"`
        Surname string `edn:"surname"`
}
 
func main() {
        m1 := make(map[string]User3)
 
        m1["user-id-1"] = User3{
                ID:      1,
                Name:    "Pepek",
                Surname: "Vyskoč"}
 
        m1["user-id-3"] = User3{
                ID:      2,
                Name:    "Josef",
                Surname: "Vyskočil"}
 
        fmt.Println("users map")
        usersMapEDN, _ := edn.Marshal(m1)
        fmt.Println(string(usersMapEDN))
        fmt.Println()
        usersPrettyEDN, _ := edn.MarshalPPrint(m1, nil)
        fmt.Println(string(usersPrettyEDN))
        fmt.Println()
}

Výsledkem je mapa ve formátu EDN, která je v prvním případě nenaformátovaná a ve druhém naopak naformátovaná a tedy uživatelsky čitelná:

{"user-id-1"{:id 1 :user-name"Pepek":surname"Vyskoč"}"user-id-3"{:id 2 :user-name"Josef":surname"Vyskočil"}}
 
{"user-id-1" {:id 1,
              :user-name "Pepek",
              :surname "Vyskoč"},
 "user-id-3" {:id 2,
              :user-name "Josef",
              :surname "Vyskočil"}}

11. Import dat ve formátu EDN do aplikace psané v Go

V této části článku se zaměříme na popis importu dat z formátu EDN do interních datových struktur programovacího jazyka Go. Pro tuto operaci, která se nazývá unmarshaling s následující 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.

12. Import jednoduché struktury (záznamu) se známým obsahem, import polí

Nejjednodušší je situace ve chvíli, kdy přesně (tedy dopředu – již v čase vývoje aplikace) známe strukturu dat. Pokud se v EDN přenáší informace o struktuře (záznamu, objektu), lze tuto strukturu deklarovat jako datový typ, vytvořit proměnnou tohoto typu a následně zavolat výše zmíněnou funkci Unmarshal:

type User struct {
   ...
   ...
   ...
}
 
var user User
edn.Unmarshal(bytes, &user)

Podívejme se nyní, jak by mohl vypadat celý demonstrační příklad, v němž je EDN načten ze souboru a zpracován:

package main
 
import (
        "fmt"
        "io/ioutil"
        "log"
 
        "olympos.io/encoding/edn"
)
 
// User je uživatelsky definovaná datová struktura s viditelnými atributy
type User struct {
        ID      uint32 `edn:"id"`
        Name    string `edn:"user-name"`
        Surname string `edn:"surname"`
}
 
func main() {
        inputEdnAsBytes, err := ioutil.ReadFile("user.edn")
        if err != nil {
                log.Fatal(err)
        }
 
        fmt.Println("Input (bytes):")
        fmt.Println(inputEdnAsBytes)
 
        fmt.Println("\nInput (string):")
        fmt.Println(string(inputEdnAsBytes))
 
        var user User
        edn.Unmarshal(inputEdnAsBytes, &user)
 
        fmt.Println("\nOutput:")
        fmt.Println(user)
 
        fmt.Println("\nFields:")
        fmt.Printf("ID:      %d\n", user.ID)
        fmt.Printf("Name:    %s\n", user.Name)
        fmt.Printf("Surname: %s\n", user.Surname)
}

Pro vstup:

{:id 1,
 :user-name "Pepek",
 :surname "Vyskoč"}

Dostaneme tyto výsledky:

Input (bytes):
[123 58 105 100 32 49 44 10 32 58 117 115 101 114 45 110 97 109 101 32 34 80 101 112 101 107 34 44 10 32 58 115 117 114 110 97 109 101 32 34 86 121 115 107 111 196 141 34 125 10 10]
 
Input (string):
{:id 1,
 :user-name "Pepek",
 :surname "Vyskoč"}
 
 
 
Output:
{1 Pepek Vyskoč}
 
Fields:
ID:      1
Name:    Pepek
Surname: Vyskoč
Poznámka: povšimněte si, že je možné snadno přistupovat k prvkům struktury, protože překladač zná její předpokládaný obsah. V dalším příkladu to již takto jednoduše možné nebude.

Při importu polí využijeme skutečnosti, že v případě použití řezů je možné pole, které je řezem používáno, automaticky zvětšovat při přidávání nových prvků. Tuto operaci za nás provede přímo knihovna pro unmarshalling, takže se nemusíme zabývat (před)alokací pole:

var numbers []int
edn.Unmarshal(input_json_as_bytes, &numbers)

13. Načtení předem neznámé struktury ze souboru ve formátu EDN

Nakonec se podívejme, jak se načítá obsah souboru uloženého ve formátu EDN s předem neznámou strukturou. V tomto případě použijeme mapu, jejímiž klíči musí být řetězce a hodnotami libovolný typ implementující prázdné rozhraní interface{}:

m1 := map[string]interface{}{}
Poznámka: dvojice složených závorek je napsána naschvál – první závorky jsou součástí typu, druhé znamenají inicializaci mapy.

Problém spočívá v další interpretaci hodnot, kdy je nutné použít obdobu reflexe. Nicméně alespoň základní práci s takto načtenou mapou lze zajistit:

package main
 
import (
        "fmt"
        "io/ioutil"
        "log"
 
        "olympos.io/encoding/edn"
)
 
func main() {
        inputEdnAsBytes, err := ioutil.ReadFile("user.edn")
        if err != nil {
                log.Fatal(err)
        }
 
        fmt.Println("Input (bytes):")
        fmt.Println(inputEdnAsBytes)
 
        fmt.Println("\nInput (string):")
        fmt.Println(string(inputEdnAsBytes))
 
        user := map[interface{}]interface{}{}
        edn.Unmarshal(inputEdnAsBytes, &user)
 
        fmt.Println("\nOutput:")
        fmt.Println(user)
 
        fmt.Println("\nFields:")
        for key, value := range user {
                fmt.Printf("%v\t%v\n", key, value)
        }
}

Opět si vyzkoušejme použít tento vstupní soubor:

{:id 1,
 :user-name "Pepek",
 :surname "Vyskoč"}

Tato mapa by měla být korektně načtena a zpracována:

Input (bytes):
[123 58 105 100 32 49 44 10 32 58 117 115 101 114 45 110 97 109 101 32 34 80 101 112 101 107 34 44 10 32 58 115 117 114 110 97 109 101 32 34 86 121 115 107 111 196 141 34 125 10 10]
 
Input (string):
{:id 1,
 :user-name "Pepek",
 :surname "Vyskoč"}
 
 
 
Output:
map[:id:1 :surname:Vyskoč :user-name:Pepek]
 
Fields:
:user-name      Pepek
:surname        Vyskoč
:id     1
Poznámka: teoreticky lze načíst data s libovolnou strukturou do proměnné typu interface{}, jenže zpracování takových dat není vůbec přímočaré – pro tyto účely se (alespoň podle mého názoru) Go, jako většinou staticky a silně typovaný jazyk, příliš nehodí.

14. Problematika načítání EDN v jazycích se sémantikou odlišnou od Clojure

V úvodním článku o formátu EDN jsme si řekli, že je tento formát do značné míry odvozený od vlastností programovacího jazyka Clojure. Tento jazyk přináší vývojářům několik zajímavých vlastností, které ovšem do značné míry určují i vlastnosti EDN – a hlavní problém spočívá v tom, že některé z těchto vlastností nejsou v jiných jazycích podporovány, takže čtečky (či konvertory) EDN musí tato omezení nějakým způsobem obcházet. Abychom byli konkrétnější, jsou v následujících bodech vypsány některé vlastnosti EDN, které mohou být v jiných programovacích jazycích při práci s EDN problematické:

  1. Ve formátu EDN je možné používat jak „keywords“, tak i řetězce, ovšem mnohé další jazyky „keywords“ nepodporují a v některých případech dokonce automaticky převádí jejich hodnotu na řetězce. To je pochopitelně nekorektní, protože nám to neumožní rozlišit například mezi hodnotou :foo a „foo“; tudíž nelze tyto dvě hodnoty uložit do jedné množiny, použít je jako klíče v jedné mapě atd.
  2. Ve formátu EDN se taktéž striktně rozlišuje mezi celočíselnými hodnotami a numerickými hodnotami s plovoucí řádovou čárkou. To například znamená, že hodnota 42 je odlišná od hodnoty 42.0 a tudíž je možné obě tyto hodnoty použít jako (rozdílné) klíče v jediné mapě či jako dva prvky v množině. Pro ty programovací jazyky, které mají pouze jeden numerický datový typ, je toto rozlišení minimálně problematické.
  3. EDN podporuje čtyři typy kontejnerů, a to konkrétně seznamy, vektory, mapy a množiny. Většina ostatních jazyků, které EDN podporují, dokáže pracovat se seznamy a vektory jako s poli (což je v pořádku, liší se jen způsob uložení hodnot) a s mapami (nazývanými většinou asociativní pole). Práce s množinami tedy musí být řešena tak, že se tato hodnota převede na vhodný objekt.
  4. Již několikrát jsme se v předchozích odstavcích zmínili o mapách. V EDN je možné použít jakékoli hodnoty ve funkci klíče nějakého prvku. To znamená, že klíčem může být například hodnota nil, pochopitelně řetězec, ale i vektor, mapa, mapa obsahující jako své prvky další mapu atd. atd. A tato vlastnost není v mnoha dalších programovacích jazycích, které s EDN pracují, korektně implementována, což znamená, že není možné mapy z EDN přímo převádět na asociativní pole v těchto programovacích jazycích.

V navazujících kapitolách si ukážeme příklady některých EDN, které nejsou či nemusí být ve všech případech správně zpracovány (což do značné míry souvisí se sémantikou daných programovacích jazyků).

15. Konverze hodnot prováděné v Pythonu

Pro otestování vlastností knihovny edn_format určené pro programovací jazyk Python, kterou jsme si popsali minule, použijeme následující jednoduchý skript, který načte soubor ve formátu EDN a zobrazí formu načtených dat:

#!/usr/bin/env python3
 
# Converts structured data from EDN format into JSON format.
 
import sys
import edn_format
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) > 2:
    print("Usage:")
    print("  print_edn.py input_file.edn")
    print("Example:")
    print("  print_edn.py report.edn")
    sys.exit(1)
 
# First command line argument should contain name of input EDN file.
filename = sys.argv[1]
 
 
# 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(payload)

Začneme mapou obsahující celočíselné hodnoty i hodnoty s plovoucí řádovou čárkou:

Vstup:

{"foo" 42
 "bar" 42.0}

Výstup:

{'foo': 42, 'bar': 42.0}

V předchozím příkladu by vše v pořádku, takže zkusme použití numerických hodnot ve funkci klíčů:

Vstup:

{42 "a"
 42.0 "b"}

Výstup:

{42: 'b'}

V tomto případě došlo ke konverzi a ztrátě jedné z hodnot v mapě.

Použití keywords ve funkci klíčů:

Vstup:

{:foo "a"
 :bar "b"}

Výstup:

{Keyword(foo): 'a', Keyword(bar): 'b'}

Vše je v pořádku – keywords jsou reprezentovány objektem.

Kombinace keyword a řetězce ve funkci klíče:

Vstup:

{:foo "a"
 "foo" "b"}

Výstup:

{Keyword(foo): 'a', 'foo': 'b'}

Použití hodnoty nil:

Vstup:

{nil "a"
 "foo" "b"
 "nic" nil}

Výstup:

{None: 'a', 'foo': 'b', 'nic': None}

Opět můžeme vidět, že je vše v pořádku.

16. Načtení složitějších datových struktur

Mapa se složenými klíči obsahujícími různé typy hodnot:

Vstup:

{nil       "a"
 "foo"     "b"
 ["a" "b"] "c"
 [1 2 3]   4
 #{1 2 3}  4
 "nic"     nil}

Výstup:

{None: 'a', 'foo': 'b', ['a', 'b']: 'c', [1, 2, 3]: 4, frozenset({1, 2, 3}): 4, 'nic': None}

Vstupem nemusí být pouze mapa, ale například přímo množina:

#{nil "a" "foo" "b" ["a" "b"] "c" [1 2 3] 4 #{1 2 3} 5 "nic" :nil}

Výstup:

frozenset({'foo', 4, 5, 'a', frozenset({1, 2, 3}), 'nic', [1, 2, 3], None, ['a', 'b'], Keyword(nil), 'b', 'c'})
Poznámka: v tomto případě je použit objekt typu frozenset ze standardní knihovny programovacího jazyka Python.

Často se taktéž setkáme s maticemi:

[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]
 [13 14 15 16]]

Výstup:

[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]

Nebo se seznamy seznamů:

(( 1  2  3  4)
 ( 5  6  7  8)
 ( 9 10 11 12)
 (13 14 15 16))

Výstup:

((1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12), (13, 14, 15, 16))

A nakonec reálná data používaná v ekosystému programovacího jazyka Clojure:

{: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
 :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
 :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"
}

Výstup:

{Keyword(aliases): {'downgrade': 'upgrade'},
 Keyword(checkout-deps-shares): [Keyword(source-paths),
                                 Keyword(test-paths),
                                 Keyword(resource-paths),
                                 Keyword(compile-path),
                                 "#'leiningen.core.classpath/checkout-deps-paths"],
 Keyword(clean-targets): [Keyword(target-path)],
 Keyword(compile-path): '/home/ptisnovs/src/presentations/edn/edn2json/target/default/classes',
 Keyword(dependencies): ([Symbol(org.clojure/clojure), '1.10.1'],
                         [Symbol(org.clojure/data.json), '2.2.0'],
                         [Symbol(nrepl/nrepl), '0.7.0', Keyword(exclusions), ([Symbol(org.clojure/clojure)],)],
                         [Symbol(clojure-complete/clojure-complete), '0.2.5', Keyword(exclusions),
                         ([Symbol(org.clojure/clojure)],)], [Symbol(venantius/ultra), '0.6.0']),
 ...
 ...
 ...
Poznámka: povšimněte si, že i symboly jsou zpracovány a reprezentovány formou objektu.

17. Uživatelem definované štítky (tagy)

V úvodním článku jsme se zmínili o možnosti rozšiřovat formát EDN o nové uživatelem definované datové typy. Ty jsou přidávány s využitím takzvaných štítků neboli tagů. Štítek začíná znakem #, za nímž (ihned) následuje jméno štítku a taktéž data, která mohou být ke štítku přiřazena. Již v základní knihovně EDN existují dva rozšiřující štítky a tedy i dva nové 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 rozšiřujících typů je UUID, který se dnes skutečně využívá univerzálně:

#uuid "01234567-89ab-cdef-0123-456789abcdef"

Ovšem nic nám nebrání v tom vytvořit a používat vlastní štítky, například štítek uvozující nový datový typ komplexní číslo, popř. štítek specifikující uživatele (a vytvářející tak vlastně nový datový typ s hodnotou typu řetězec):

[#complex {:real 10.0 :imag 20.0}
 #user "root"]

Alternativně je možné u štítků používat jmenné prostory, což je u větších aplikací (a při případné snaze o standardizaci štítků) velmi výhodné:

[#cz.root/complex {:real 10.0 :imag 20.0}
 #cz.root/user "root"]

Podpora pro štítky ve formátu EDN existuje v knihovnách připravených pro jazyk Clojure, Go i Python. V případě jazyka Go je nutné podporu pro zpracování štítků realizovat přímo v programu, který EDN načítá. Konkrétně je nutné zaregistrovat funkci, která příslušný štítek (resp. jeho obsah) zpracuje. To se provádí následujícím způsobem (příklad pro komplexní čísla):

intoComplex := func(v [2]float64) (complex128, error) {
        return complex(v[0], v[1]), nil
}
 
rdr := strings.NewReader(input)
dec := edn.NewDecoder(rdr)
 
dec.AddTagFn("complex", intoComplex)
Poznámka: zde již začíná být patrné, jak silný je vlastně koncept štítků v kontextu rozšiřitelného datového formátu.

18. Práce s uživatelskými štítky v Pythonu

V případě, že se soubor uložený ve formátu EDN, jenž obsahuje „oštítkovaná data“, pokusíme načíst v našem jednoduchém skriptu, dojde k běhové chybě:

$ ./print_edn.py t1.edn 
 
Traceback (most recent call last):
  File "./print_edn.py", line 23, in
    payload = edn_format.loads(edn_input.read())
  File "/home/ptisnovs/.local/lib/python3.6/site-packages/edn_format/edn_parse.py", line 244, in parse
    expressions = parse_all(text, **kwargs)
  File "/home/ptisnovs/.local/lib/python3.6/site-packages/edn_format/edn_parse.py", line 234, in parse_all
    expressions = p.parse(text, lexer=lex())
  File "/home/ptisnovs/.local/lib/python3.6/site-packages/ply/yacc.py", line 333, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
  File "/home/ptisnovs/.local/lib/python3.6/site-packages/ply/yacc.py", line 1120, in parseopt_notrack
    p.callable(pslice)
  File "/home/ptisnovs/.local/lib/python3.6/site-packages/edn_format/edn_parse.py", line 199, in p_expression_tagged_element
    u"Don't know how to handle tag ImmutableDict({})".format(tag))
NotImplementedError: Don't know how to handle tag ImmutableDict(complex)

Aby skript formát EDN načetl, je nutné do něj přidat podporu pro oba uživatelsky definované štítky. To je relativně jednoduché, neboť každý štítek je realizován třídou odvozenou od třídy TaggedElement, ke které je štítek přiřazen s využitím dekorátoru:

bitcoin_skoleni

@tag("user")
class User(TaggedElement):
    def __init__(self, value):
        print("This is user:", value)
        self.value=value
 
 
@tag("complex")
class Complex(TaggedElement):
    def __init__(self, value):
        print("This is complex number:", value)
        self.value=value
Poznámka: pochopitelně by se v praxi příslušné třídy rozšířily takovým způsobem, aby skutečně nesly načtenou hodnotu štítku ve zpracovatelné podobě.

Upravený skript může vypadat následovně:

import sys
import edn_format
 
from edn_format import tag, TaggedElement
 
 
@tag("user")
class User(TaggedElement):
    def __init__(self, value):
        print("This is user:", value)
        self.value=value
 
 
@tag("complex")
class Complex(TaggedElement):
    def __init__(self, value):
        print("This is complex number:", value)
 
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  print_edn.py input_file.edn")
    print("Example:")
    print("  print_edn.py report.edn")
    sys.exit(1)
 
# First command line argument should contain name of input EDN file.
filename = sys.argv[1]
 
 
# 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(payload)

19. Repositář s demonstračními příklady

Zdrojové kódy všech minule i 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/pre­sentations:

# 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/pre­sentations/blob/master/ed­n/json2edn.py
2 edn2json.py konverze mezi formátem EDN a JSON naprogramovaná v Pythonu https://github.com/tisnik/pre­sentations/blob/master/ed­n/edn2json.py
       
3 json2edn (adresář) konverze mezi formátem JSON a EDN naprogramovaná v Clojure https://github.com/tisnik/pre­sentations/blob/master/ed­n/json2edn
4 edn2json (adresář) konverze mezi formátem EDN a JSON naprogramovaná v Clojure https://github.com/tisnik/pre­sentations/blob/master/ed­n/edn2json
       
5 properties2edn (adresář) konverze mezi .properties souborem a formátem EDN (Clojure) https://github.com/tisnik/pre­sentations/blob/master/ed­n/properties2edn
6 xml2edn (adresář) konverze mezi XML a formátem EDN (Clojure, plná konverze) https://github.com/tisnik/pre­sentations/blob/master/ed­n/xml2edn
7 forest-demo (adresář) různé možnosti konverze mezi XML a formátem EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/forest-demo
       
8 go-edn-basic-types-1 (adresář) serializace (marshalling) celočíselných datových typů do formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-basic-types-1
9 go-edn-basic-types-2 (adresář) serializace (marshalling) celočíselných datových typů do formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-basic-types-2
10 go-edn-basic-types-3 (adresář) serializace (marshalling) komplexních čísel do formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-basic-types-3
11 go-edn-basic-types-4 (adresář) serializace (marshalling) hodnot nil a řetězců do formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-basic-types-4
12 go-edn-arrays (adresář) serializace (marshalling) polí do formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-arrays
       
13 go-edn-1 (adresář) serializace (marshalling) datové struktury do formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-1
14 go-edn-2 (adresář) specifikace názvů klíčů v EDN formátu https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-2
15 go-edn-3 (adresář) uložení pole datových struktur do formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-3
16 go-edn-4 (adresář) uložení mapy datových struktur do formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-4
       
17 go-edn-5 (adresář) deserializace (unmarshalling) známé datové struktury z formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-5
18 go-edn-6 (adresář) deserializace (unmarshalling) neznámé datové struktury z formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/go-edn-6
       
19 print_edn.py skript, který vytiskne obsah vybraného souboru uloženého ve formátu EDN https://github.com/tisnik/pre­sentations/blob/master/ed­n/print_edn.py

20. Odkazy na Internetu

  1. edn – extensible data notation
    https://github.com/edn-format/edn
  2. Programming with Data and EDN
    https://docs.datomic.com/clou­d/whatis/edn.html
  3. Video about EDN
    https://docs.datomic.com/clou­d/livetutorial/edntutorial­.html
  4. (Same) video about EDN on Youtube
    https://www.youtube.com/wat­ch?v=5eKgRcvEJxU
  5. clojure.edn
    https://clojuredocs.org/clojure.edn
  6. API for clojure.edn – Clojure v1.10.2 (stable)
    https://clojure.github.io/clo­jure/clojure.edn-api.html
  7. Clojure EDN Walkthrough
    https://www.compoundtheory.com/clojure-edn-walkthrough/
  8. Články týkající se Pythonu na Rootu
    https://www.root.cz/n/python/
  9. Články týkající se programovacího jazyka Clojure na Rootu
    https://www.root.cz/n/clojure/
  10. Seriál Programovací jazyk Go
    https://www.root.cz/seria­ly/programovaci-jazyk-go/
  11. Crux
    https://opencrux.com/main/index.html
  12. Crux Installation
    https://opencrux.com/reference/21.04–1.16.0/installation.html
  13. read
    https://clojuredocs.org/clo­jure.edn/read
  14. read-string
    https://clojuredocs.org/clo­jure.edn/read-string
  15. Tupelo 21.04.12 (dokumentace)
    https://cloojure.github.i­o/doc/tupelo/
  16. tupelo – Clojure With A Spoonful of Honey
    https://clojars.org/tupelo
  17. Clojure Cookbook: Templating HTML with Enlive
    https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc
  18. An Introduction to Enlive
    https://github.com/swannodette/enlive-tutorial/
  19. Enlive na GitHubu
    https://github.com/cgrand/enlive
  20. data.json
    https://github.com/clojure/data.json
  21. data.json API reference
    https://clojure.github.io/data.json/
  22. Clojure: Writing JSON to a File/Reading JSON From a File
    https://dzone.com/articles/clojure-writing-json
  23. How to pretty print JSON to a file in Clojure?
    https://stackoverflow.com/qu­estions/23307552/how-to-pretty-print-json-to-a-file-in-clojure
  24. go-edn / edn
    https://github.com/go-edn/edn
  25. Queries (Crux)
    https://opencrux.com/reference/21.04–1.16.0/queries.html
  26. Essential EDN
    https://opencrux.com/tuto­rials/essential-edn.html
  27. 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/
  28. Introducing JSON
    https://www.json.org/json-en.html
  29. ISO 8601
    https://xkcd.com/1179/
  30. What is the right JSON date format
    https://stackoverflow.com/qu­estions/10286204/what-is-the-right-json-date-format
  31. ClojureScript REPL
    https://clojurescript.io/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.