Hlavní navigace

Vývoj síťových aplikací v programovacím jazyku Go (práce s JSONem a rastrovými obrázky)

26. 2. 2019
Doba čtení: 37 minut

Sdílet

 Autor: Go Lang
Při tvorbě webových služeb se v naprosté většině případů nevyhneme použití formátu JSON. Ukážeme si, jak se s ním pracuje v jazyku Go. Také si vysvětlíme problematiku generování rastrových obrázků ve „webových“ formátech.

Obsah

1. Zpracování JSON formátu v programovacím jazyce Go

2. „Marshalling“ datových struktur do formátu JSON

3. Převod celočíselných hodnot do JSONu

4. Chování systému při pokusu o převod hodnot, které nemají podporu v JSONu

5. Převod polí a řezů do JSONu

6. Struktury (záznamy) a jejich přímý převod do JSONu

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

8. Složitější (vnořené) datové struktury, změna názvů klíčů ve výsledném JSONu

9. Export speciálních hodnot do JSONu

10. Import dat ve formátu JSON

11. Import jednoduché struktury (záznamu) se známým obsahem

12. Import polí z JSONu

13. Import map obsahujících struktury (záznamy)

14. Specifikace klíčů zapsaných v souborech JSON

15. Načtení předem neznámé struktury z JSONu

16. Práce s rastrovými obrázky

17. Export rastrového obrazu do PNG

18. Export rastrového obrazu do formátu JPEG

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

20. Odkazy na Internetu

1. Zpracování JSON formátu v programovacím jazyce Go

V první části dnešního článku se zaměříme na popis způsobů použití formátu JSON v aplikacích vyvinutých v programovacím jazyku Go. Jedná se o poměrně důležitou oblast, protože JSON (a samozřejmě též XML) se v současnosti používá v mnoha webových službách a i když stále vznikají a jsou postupně adaptovány další formáty, ať již textové (YAML, edn) či binární (BSON, B-JSON, Smile, Protocol-Buffers), je velmi pravděpodobné, že se JSON bude i nadále poměrně masivně využívat. Formát JSON tak, jak ho budeme zpracovávat v dnešních demonstračních příkladech, je stručně a přitom dostatečně přesně popsán na známé a často navštěvované stránce http://json.org/.

Poznámka: v některých případech se setkáme s různými omezeními na straně některých implementací knihoven pro práci s JSONem. Týká se to především těch programovacích jazyků, které používají asociativní pole (mapy) v JSONu pro ukládání atributů objektů – a pro názvy atributů jsou pochopitelně vyhrazeny jen některé znaky (typicky se zde nesmí vyskytovat mezery a někdy ani pomlčka či lomítko). Nám však tato omezení při použití jazyka Go nebudou žádným způsobem vadit, protože klíči map mohou být libovolné řetězce.

Příklady reálných dat uložených ve formátu JSON není těžké získat. Ukážeme si pouze tři příklady získané z veřejně dostupných webových služeb.

Využití služby, která vrátí hlavičky posílané klientem:

$ curl http://httpbin.org/get
 
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.35.0"
  },
  "origin": "89.24.49.226, 89.24.49.226",
  "url": "https://httpbin.org/get"
}

Služba vracející dokument reprezentovaný ve formátu JSON:

$ curl http://httpbin.org/json
 
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Sample Slide Show"
  }
}

Veřejně dostupné REST API nalezneme i v případě GitHubu:

$ curl https://api.github.com/users/torvalds
 
{
  "login": "torvalds",
  "id": 1024025,
  "node_id": "MDQ6VXNlcjEwMjQwMjU=",
  "avatar_url": "https://avatars0.githubusercontent.com/u/1024025?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/torvalds",
  "html_url": "https://github.com/torvalds",
  "followers_url": "https://api.github.com/users/torvalds/followers",
  "following_url": "https://api.github.com/users/torvalds/following{/other_user}",
  "gists_url": "https://api.github.com/users/torvalds/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/torvalds/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/torvalds/subscriptions",
  "organizations_url": "https://api.github.com/users/torvalds/orgs",
  "repos_url": "https://api.github.com/users/torvalds/repos",
  "events_url": "https://api.github.com/users/torvalds/events{/privacy}",
  "received_events_url": "https://api.github.com/users/torvalds/received_events",
  "type": "User",
  "site_admin": false,
  "name": "Linus Torvalds",
  "company": "Linux Foundation",
  "blog": "",
  "location": "Portland, OR",
  "email": null,
  "hireable": null,
  "bio": null,
  "public_repos": 6,
  "public_gists": 0,
  "followers": 88851,
  "following": 0,
  "created_at": "2011-09-03T15:26:22Z",
  "updated_at": "2019-01-23T02:49:04Z"
}

2. „Marshalling“ datových struktur do formátu JSON

Pro práci s formátem JSON v programovacím jazyku Go slouží standardní balíček nazvaný encoding/json. Všechny dnes ukázané demonstrační příklady tedy budou tento balíček importovat:

import (
        ...
        "encoding/json"
        ...
)

Formát JSON umožňuje uložení a tím pádem i přenos jediné (nijak nepojmenované) hodnoty. Podporovány jsou přitom hodnoty, které můžeme zařadit do šesti kategorií (viz též příslušná část graficky vyjádřené syntaxe formátu JSON):

# Hodnota Stručný popis
1 string řetězec (s plnou podporou Unicode)
2 number celé číslo popř. hodnota typu double
3 object ve skutečnosti se jedná o asociativní pole (mapu), viz poznámka v úvodní kapitole
4 array pole, ovšem v JSONu nemusí mít všechny prvky pole stejný typ
5 true, false pravdivostní hodnota
6 null prázdná hodnota
Poznámka: složitější strukturu lze získat snadno – atributy objektů (prvky asociativních polí) i prvky polí totiž mohou obsahovat libovolné hodnoty, takže jsou plně podporovány rekurzivní struktury (pole objektů, pole polí, mapy struktur/záznamů atd.).

Pro převod libovolného typu (přesněji řečeno hodnoty libovolného typu) do JSONu se používá funkce nazvaná Marshal, kterou nalezneme v balíčku encoding/json:

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í.

V typických zdrojových kódech se tedy setkáme s tímto idiomatickým zápisem:

json_bytes, err := json.Marshal(a)
 
if err != nil {
        log.Fatal(err)
}
...
...
...
Poznámka: to, že funkci Marshal můžeme zavolat a předat jí libovolnou hodnotu ovšem v žádném případě neznamená, že převod bude skutečně proveden. Některé typy hodnot totiž nemají v JSONu svoji obdobu (je to například funkce nebo ukazatel). Podrobnosti o podporovaných a nepodporovaných typech budou vysvětleny v navazujících kapitolách.
Poznámka2: jméno této metody odpovídá prováděné operaci, takzvanému marshallingu. Opačná operace, tj. převod dat z JSONu do datových struktur Go, se nazývá unmarshalling.

3. Převod celočíselných hodnot do JSONu

V dnešním prvním demonstračním příkladu, jehož zdrojový kód naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article13/01_json_mar­shal_basic_signed_types.go je ukázán způsob konverze celých čísel se znaménkem do formátu JSON. 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):

package main
 
import (
        "encoding/json"
        "fmt"
)
 
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'
 
        a_json, _ := json.Marshal(a)
        fmt.Println(string(a_json))
 
        b_json, _ := json.Marshal(b)
        fmt.Println(string(b_json))
 
        c_json, _ := json.Marshal(c)
        fmt.Println(string(c_json))
 
        d_json, _ := json.Marshal(d)
        fmt.Println(string(d_json))
 
        r1_json, _ := json.Marshal(r1)
        fmt.Println(string(r1_json))
 
        r2_json, _ := json.Marshal(r2)
        fmt.Println(string(r2_json))
 
        r3_json, _ := json.Marshal(r3)
        fmt.Println(string(r3_json))
 
        r4_json, _ := json.Marshal(r4)
        fmt.Println(string(r4_json))
}
Povšimněte si, že ignorujeme případný chybový stav (namísto err použijeme zástupný znak _), což si můžeme dovolit pouze v těchto jednoduchých příkladech, ale v praxi je samozřejmě chyby nutné kontrolovat.

Po spuštění demonstračního příkladu by se mělo zobrazit osm textových řádků, z nichž každý reprezentuje validní JSON:

-10
-1000
-10000
-1000000
97
64
10
955

Druhý demonstrační příklad, který najdete na adrese https://github.com/tisnik/go-fedora/blob/master/article13/02_json_mar­shal_basic_unsigned_types­.go, 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 (
        "encoding/json"
        "fmt"
)
 
func main() {
        var b8 byte = 0x42
 
        var a uint8 = 10
        var b uint16 = 1000
        var c uint32 = 10000
        var d uint32 = 1000000
 
        b8_json, _ := json.Marshal(b8)
        fmt.Println(string(b8_json))
 
        a_json, _ := json.Marshal(a)
        fmt.Println(string(a_json))
 
        b_json, _ := json.Marshal(b)
        fmt.Println(string(b_json))
 
        c_json, _ := json.Marshal(c)
        fmt.Println(string(c_json))
 
        d_json, _ := json.Marshal(d)
        fmt.Println(string(d_json))
}

Po spuštění tohoto příkladu získáme pět řádků – pět korektních JSONů:

66
10
1000
10000
1000000
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í podporu v JSONu

Hodnoty některých datových typů programovacího jazyka Go ovšem nemají ve formátu JSON 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

Převod (prozatím bez jakékoli kontroly 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 (
        "encoding/json"
        "fmt"
)
 
func main() {
        var a complex64 = -1.5 + 0i
        var b complex64 = 1.5 + 1000i
        var c complex64 = 1e30 + 1e30i
        var d complex64 = 1i
 
        a_json, _ := json.Marshal(a)
        fmt.Println(string(a_json))
 
        b_json, _ := json.Marshal(b)
        fmt.Println(string(b_json))
 
        c_json, _ := json.Marshal(c)
        fmt.Println(string(c_json))
 
        d_json, _ := json.Marshal(d)
        fmt.Println(string(d_json))
}

Po spuštění získáme pouze čtveřici prázdných řádků (tedy prázdných řetězců), což samozřejmě neodpovídá očekávaným číselným hodnotám:

Z tohoto důvodu zdrojový kód programu nepatrně upravíme takovým způsobem, aby bylo možné zkontrolovat chyby, k nimž může při převodu (tedy při marshallingu) do JSONu dojít:

package main
 
import (
        "encoding/json"
        "fmt"
)
 
func main() {
        var a complex64 = -1.5 + 0i
        var b complex64 = 1.5 + 1000i
        var c complex64 = 1e30 + 1e30i
        var d complex64 = 1i
 
        a_json, a_err := json.Marshal(a)
        fmt.Println(string(a_json))
        fmt.Println(a_err)
 
        b_json, b_err := json.Marshal(b)
        fmt.Println(string(b_json))
        fmt.Println(b_err)
 
        c_json, c_err := json.Marshal(c)
        fmt.Println(string(c_json))
        fmt.Println(c_err)
 
        d_json, d_err := json.Marshal(d)
        fmt.Println(string(d_json))
        fmt.Println(d_err)
}

Po spuštění takto upravené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:

 
json: unsupported type: complex64
 
json: unsupported type: complex64
 
json: unsupported type: complex64
 
json: unsupported type: complex64

5. Převod polí a řezů do JSONu

S JSONy obsahujícími jedinou skalární hodnotu se nesetkáme příliš často (i když některé webové služby vrací jednoduché řetězce obsahující stav aplikace), 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 JSONu 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 JSONu 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 čtvrtého demonstračního příkladu, v němž k marshallingu polí dochází:

package main
 
import (
        "encoding/json"
        "fmt"
)
 
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},
        }
 
        a1_json, _ := json.Marshal(a1)
        fmt.Println(string(a1_json))
 
        a2_json, _ := json.Marshal(a2)
        fmt.Println(string(a2_json))
 
        a3_json, _ := json.Marshal(a3)
        fmt.Println(string(a3_json))
 
        a4_json, _ := json.Marshal(a4)
        fmt.Println(string(a4_json))
 
        a5_json, _ := json.Marshal(a5)
        fmt.Println(string(a5_json))
 
        matice_json, _ := json.Marshal(matice)
        fmt.Println(string(matice_json))
}

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ě (existuje i druhá varianta s apostrofy namísto uvozovek):

["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 JSONu 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í:

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

6. Struktury (záznamy) a jejich přímý převod do JSONu

Do JSONu pochopitelně můžeme převádět i jednotlivé struktury. Struktura se zkonvertuje do JSONu ve formě objektu (což je označení pro hodnotu s atributy). 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í pátého demonstračního příkladu:

package main
 
import (
        "encoding/json"
        "fmt"
)
 
type User1 struct {
        id      uint32
        name    string
        surname string
}
 
type User2 struct {
        Id      uint32
        Name    string
        Surname string
}
 
func main() {
        user1 := User1{
                1,
                "Pepek",
                "Vyskoč"}
 
        user2 := User2{
                1,
                "Pepek",
                "Vyskoč"}
 
        user1_json, _ := json.Marshal(user1)
        fmt.Println(string(user1_json))
 
        user2_json, _ := json.Marshal(user2)
        fmt.Println(string(user2_json))
}

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č"}
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.

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

package main
 
import (
        "encoding/json"
        "fmt"
)
 
type User struct {
        Id      uint32
        Name    string
        Surname string
}
 
func main() {
        var users = [3]User{
                User{
                        Id:      1,
                        Name:    "Pepek",
                        Surname: "Vyskoč"},
                User{
                        Id:      2,
                        Name:    "Pepek",
                        Surname: "Vyskoč"},
                User{
                        Id:      3,
                        Name:    "Josef",
                        Surname: "Vyskočil"},
        }
 
        users_json, _ := json.Marshal(users)
        fmt.Println(string(users_json))
}

S výsledkem (JSON není nijak naformátovaný):

[{"Id":1,"Name":"Pepek","Surname":"Vyskoč"},{"Id":2,"Name":"Pepek","Surname":"Vyskoč"},{"Id":3,"Name":"Josef","Surname":"Vyskočil"}]

Výsledek po naformátování:

[
  {
    "Id": 1,
    "Name": "Pepek",
    "Surname": "Vyskoč"
  },
  {
    "Id": 2,
    "Name": "Pepek",
    "Surname": "Vyskoč"
  },
  {
    "Id": 3,
    "Name": "Josef",
    "Surname": "Vyskočil"
  }
]
Poznámka: pro naformátování jsem použil nástroj nabízený službou https://duckduckgo.com/.

Opět platí, že prvky struktury, jejichž název začíná malým písmenem, nebudou převedeny, což je ukázáno v dalším příkladu:

package main
 
import (
        "encoding/json"
        "fmt"
)
 
type User struct {
        id      uint32
        name    string
        surname string
}
 
func main() {
        var users = [3]User{
                User{
                        id:      1,
                        name:    "Pepek",
                        surname: "Vyskoč"},
                User{
                        id:      2,
                        name:    "Pepek",
                        surname: "Vyskoč"},
                User{
                        id:      3,
                        name:    "Josef",
                        surname: "Vyskočil"},
        }
 
        users_json, _ := json.Marshal(users)
        fmt.Println(string(users_json))
}

Výsledek – pole s prázdnými objekty:

[{},{},{}]

7. 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 JSON setkáme s mapami (asociativními poli), jejichž hodnotami jsou záznamy. Mapy mají v JSONu jedno omezené – klíči mohou být řetězce (toto omezení v programovacím jazyce Go neplatí).

V dalším příkladu je ukázán převod mapy s 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 (
        "encoding/json"
        "fmt"
)
 
type User struct {
        Id      uint32
        Name    string
        Surname string
}
 
func main() {
        m1 := make(map[string]User)
 
        m1["user-id-1"] = User{
                Id:      1,
                Name:    "Pepek",
                Surname: "Vyskoč"}
 
        m1["user-id-2"] = User{
                Id:      2,
                Name:    "Josef",
                Surname: "Vyskočil"}
 
        m1_json, _ := json.Marshal(m1)
        fmt.Println(string(m1_json))
}

Výsledek:

{"user-id-1":{"Id":1,"Name":"Pepek","Surname":"Vyskoč"},"user-id-2":{"Id":2,"Name":"Josef","Surname":"Vyskočil"}}

Po naformátování:

{
  "user-id-1": {
    "Id": 1,
    "Name": "Pepek",
    "Surname": "Vyskoč"
  },
  "user-id-2": {
    "Id": 2,
    "Name": "Josef",
    "Surname": "Vyskočil"
  }
}

Dejte si ovšem skutečně pozor na to, aby klíče mapy byly skutečně řetězci. Pokud se použije jiný datový typ, nebude převod proveden, i když například použít této struktury jako klíče je v Go legální:

type Key struct {
        Id   uint32
        Role string
}

O chování funkce pro marshalling se můžeme snadno přesvědčit:

package main
 
import (
        "encoding/json"
        "fmt"
)
 
type Key struct {
        Id   uint32
        Role string
}
 
type User struct {
        Id      uint32
        Name    string
        Surname string
}
 
func main() {
        m1 := make(map[Key]User)
 
        m1[Key{1, "admin"}] = User{
                Id:      1,
                Name:    "Pepek",
                Surname: "Vyskoč"}
 
        m1[Key{2, "user"}] = User{
                Id:      2,
                Name:    "Josef",
                Surname: "Vyskočil"}
 
        m1_json, _ := json.Marshal(m1)
        fmt.Println(string(m1_json))
}

Výsledkem bude v tomto případě prázdný řádek:

8. Složitější (vnořené) datové struktury, změna názvů klíčů ve výsledném JSONu

Samozřejmě se můžeme pokusit i o marshalling struktur/záznamů, jejichž prvky jsou opět záznamy. V dalším příkladu se serializuje mapa obsahující struktury, přičemž v prvku Ids je uložena další struktura:

package main
 
import (
        "encoding/json"
        "fmt"
)
 
type Identifiers struct {
        UID     uint32
        GID     uint32
}
 
type User struct {
        Name    string
        Surname string
        Sign    []byte
        Enabled bool
        Ids     Identifiers
}
 
func main() {
        mapOfUsers := make(map[string]User)
 
        mapOfUsers["user-id-1"] = User{
                Ids:     Identifiers{1, 1},
                Name:    "Pepek",
                Surname: "Vyskoč",
                Enabled: true,
                Sign:    []byte{0,0,0,0}}
 
        mapOfUsers["user-id-2"] = User{
                Ids:     Identifiers{2, 1},
                Name:    "Josef",
                Surname: "Vyskočil",
                Enabled: false,
                Sign:    []byte{42, 10, 0, 255}}
 
        mapOfUsers["user-id-3"] = User{
                Ids:     Identifiers{3, 1},
                Name:    "Varel",
                Surname: "Frištenský"}
 
        mapOfUsers_json, _ := json.Marshal(mapOfUsers)
        fmt.Println(string(mapOfUsers_json))
}

S tímto výsledkem:

{"user-id-1":{"Name":"Pepek","Surname":"Vyskoč","Sign":"AAAAAA==","Enabled":true,"Ids":{"UID":1,"GID":1}},"user-id-2":{"Name":"Josef","Surname":"Vyskočil","Sign":"KgoA/w==","Enabled":false,"Ids":{"UID":2,"GID":1}},"user-id-3":{"Name":"Varel","Surname":"Frištenský","Sign":null,"Enabled":false,"Ids":{"UID":3,"GID":1}}}

Po naformátování:

{
  "user-id-1": {
    "Name": "Pepek",
    "Surname": "Vyskoč",
    "Sign": "AAAAAA==",
    "Enabled": true,
    "Ids": {
      "UID": 1,
      "GID": 1
    }
  },
  "user-id-2": {
    "Name": "Josef",
    "Surname": "Vyskočil",
    "Sign": "KgoA/w==",
    "Enabled": false,
    "Ids": {
      "UID": 2,
      "GID": 1
    }
  },
  "user-id-3": {
    "Name": "Varel",
    "Surname": "Frištenský",
    "Sign": null,
    "Enabled": false,
    "Ids": {
      "UID": 3,
      "GID": 1
    }
  }
}

Velmi často se ovšem setkáme s požadavkem na to, aby měly položky v JSONu odlišné označení – ostatně pojmenování položek stylem CamelCase není ve světě JSON 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 JSONu objevit:

type Identifiers struct {
        UID uint32 `json:"user-id"`
        GID uint32 `json:"group-id"`
}

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 snadno odzkoušet na mírně upraveném příkladu:

package main
 
import (
        "encoding/json"
        "fmt"
)
 
type Identifiers struct {
        UID uint32 `json:"user-id"`
        GID uint32 `json:"group-id"`
}
 
type User struct {
        Name    string `json:"user-name"`
        Surname string `json:"user-surname"`
        Sign    []byte
        Enabled bool `json:"user-login-enabled"`
        Ids     Identifiers
}
 
func main() {
        mapOfUsers := make(map[string]User)
 
        mapOfUsers["user-id-1"] = User{
                Ids:     Identifiers{1, 1},
                Name:    "Pepek",
                Surname: "Vyskoč",
                Enabled: true,
                Sign:    []byte{0, 0, 0, 0}}
 
        mapOfUsers["user-id-2"] = User{
                Ids:     Identifiers{2, 1},
                Name:    "Josef",
                Surname: "Vyskočil",
                Enabled: false,
                Sign:    []byte{42, 10, 0, 255}}
 
        mapOfUsers["user-id-3"] = User{
                Ids:     Identifiers{3, 1},
                Name:    "Varel",
                Surname: "Frištenský"}
 
        mapOfUsers_json, _ := json.Marshal(mapOfUsers)
        fmt.Println(string(mapOfUsers_json))
}

Nyní je výstup odlišný:

{"user-id-1":{"user-name":"Pepek","user-surname":"Vyskoč","Sign":"AAAAAA==","user-login-enabled":true,"Ids":{"user-id":1,"group-id":1}},"user-id-2":{"user-name":"Josef","user-surname":"Vyskočil","Sign":"KgoA/w==","user-login-enabled":false,"Ids":{"user-id":2,"group-id":1}},"user-id-3":{"user-name":"Varel","user-surname":"Frištenský","Sign":null,"user-login-enabled":false,"Ids":{"user-id":3,"group-id":1}}}

Opět si výsledek naformátujeme:

{
  "user-id-1": {
    "user-name": "Pepek",
    "user-surname": "Vyskoč",
    "Sign": "AAAAAA==",
    "user-login-enabled": true,
    "Ids": {
      "user-id": 1,
      "group-id": 1
    }
  },
  "user-id-2": {
    "user-name": "Josef",
    "user-surname": "Vyskočil",
    "Sign": "KgoA/w==",
    "user-login-enabled": false,
    "Ids": {
      "user-id": 2,
      "group-id": 1
    }
  },
  "user-id-3": {
    "user-name": "Varel",
    "user-surname": "Frištenský",
    "Sign": null,
    "user-login-enabled": false,
    "Ids": {
      "user-id": 3,
      "group-id": 1
    }
  }
}

9. Export speciálních hodnot do JSONu

Ještě si musíme ukázat chování funkce Marshal při exportu některých speciálních hodnot do formátu JSON. Nejdříve si uveďme zdrojový kód celého příkladu; pod samotným kódem jsou pak uvedeny poznámky pro každou z hodnot:

package main
 
import (
        "encoding/json"
        "fmt"
)
 
func main() {
        v1 := ""
        v2 := false
        v3 := true
        var v4 *int
 
        var slice1 []int
        slice2 := []int{}
 
        var map1 map[string]string
        map2 := make(map[string]string)
        map3 := map[string]string{}
 
        // https://speakerdeck.com/campoy/understanding-nil
        var iface interface{} = nil
 
        v1_json, _ := json.Marshal(v1)
        fmt.Println(string(v1_json))
 
        v2_json, _ := json.Marshal(v2)
        fmt.Println(string(v2_json))
 
        v3_json, _ := json.Marshal(v3)
        fmt.Println(string(v3_json))
 
        v4_json, _ := json.Marshal(v4)
        fmt.Println(string(v4_json))
 
        slice1_json, _ := json.Marshal(slice1)
        fmt.Println(string(slice1_json))
 
        slice2_json, _ := json.Marshal(slice2)
        fmt.Println(string(slice2_json))
 
        map1_json, _ := json.Marshal(map1)
        fmt.Println(string(map1_json))
 
        map2_json, _ := json.Marshal(map2)
        fmt.Println(string(map2_json))
 
        map3_json, _ := json.Marshal(map3)
        fmt.Println(string(map3_json))
 
        iface_json, _ := json.Marshal(iface)
        fmt.Println(string(iface_json))
 
        var f = func() {}
        f_json, _ := json.Marshal(f)
        fmt.Println(string(f_json))
}
Hodnota Zápis Výsledek v JSONu
prázdný řetězec "" ""
konstanta false false false
konstanta true true true
ukazatel (nulový) *int null
řez bez alokace paměti []int null
prázdný řez (nulová kapacita) []int{} []
neinicializovaná mapa (bez alokace) map[string]string null
prázdná mapa make(map[string]string) {}
prázdná mapa map[string]string{} {}
„nulové“ rozhraní interface{} null
funkce func() {} ×

10. Import dat ve formátu JSON

Ve druhé části článku se zaměříme na popis importu dat z formátu JSON do interních datových struktur programovacího jazyka Go. Pro tuto operaci, která se nazývá unmarshalling, je následující hlavička:

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 unmarshallingu 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.

11. Import jednoduché struktury (záznamu) se známým obsahem

Nejjednodušší je situace ve chvíli, kdy přesně známe strukturu dat. Pokud se v JSONu 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
json.Unmarshal(bytes, &user)

Podívejme se, jak by mohl vypadat celý příklad, v němž je JSON přímo uložen ve formě řetězcového literálu:

package main
 
import (
        "encoding/json"
        "fmt"
)
 
type User struct {
        Id      uint32
        Name    string
        Surname string
}
 
func main() {
        input_json := `{
    "Id":1,
    "Name":"Pepek",
    "Surname":"Vyskoč"
}`
        fmt.Println("Input:")
        fmt.Println(input_json)
 
        bytes := []byte(input_json)
        var user User
        json.Unmarshal(bytes, &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)
}

S výsledky:

Input:
{
    "Id":1,
    "Name":"Pepek",
    "Surname":"Vyskoč"
}
 
Output:
{1 Pepek Vyskoč}
 
Fields:
ID:      1
Name:    Pepek
Surname: Vyskoč

Samozřejmě nám nic nebrání si příslušný JSON načíst ze souboru:

package main
 
import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
)
 
type User struct {
        Id      uint32
        Name    string
        Surname string
}
 
func main() {
        input_json_as_bytes, err := ioutil.ReadFile("user.json")
        if err != nil {
                log.Fatal(err)
        }
        fmt.Println("Input (bytes):")
        fmt.Println(input_json_as_bytes)
 
        fmt.Println("\nInput (string):")
        fmt.Println(string(input_json_as_bytes))
 
        var user User
        json.Unmarshal(input_json_as_bytes, &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)
}

Příklad výstupu:

Input (bytes):
[123 10 32 32 32 32 34 73 100 34 58 49 44 10 32 32 32 32 34 78 97 109 101 34 58 34 80 101 112 101 107 34 44 10 32 32 32 32 34 83 117 114 110 97 109 101 34 58 34 86 121 115 107 111 196 141 34 10 125 10]
 
Input (string):
{
    "Id":1,
    "Name":"Pepek",
    "Surname":"Vyskoč"
}
 
 
Output:
{1 Pepek Vyskoč}
 
Fields:
ID:      1
Name:    Pepek
Surname: Vyskoč

12. Import polí z JSONu

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:

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

Opět si ukažme úplný zdrojový kód tohoto demonstračního příkladu:

package main
 
import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
)
 
func main() {
        input_json_as_bytes, err := ioutil.ReadFile("numbers.json")
        if err != nil {
                log.Fatal(err)
        }
        fmt.Println("Input (bytes):")
        fmt.Println(input_json_as_bytes)
 
        fmt.Println("\nInput (string):")
        fmt.Println(string(input_json_as_bytes))
 
        var numbers []int
        json.Unmarshal(input_json_as_bytes, &numbers)
 
        fmt.Println("\nOutput:")
        fmt.Println(numbers)
 
        fmt.Println("\nItems:")
        for i, item := range numbers {
                fmt.Printf("%d\t%d\n", i, item)
        }
}

Příklad výstupu:

Input (bytes):
[91 49 44 49 48 44 50 44 57 44 51 44 56 44 52 44 55 44 53 44 54 93 10]
 
Input (string):
[1,10,2,9,3,8,4,7,5,6]
 
 
Output:
[1 10 2 9 3 8 4 7 5 6]
 
Items:
0       1
1       10
2       2
3       9
4       3
5       8
6       4
7       7
8       5
9       6

Můžeme samozřejmě zpracovat i pole struktur, tj. tento vstupní soubor:

[
    {
        "Id":1,
        "Name":"Pepek",
        "Surname":"Vyskoč"
    },
    {
        "Id":2,
        "Name":"Pepek",
        "Surname":"Vyskoč"
    },
    {
        "Id":3,
        "Name":"Josef",
        "Surname":"Vyskočil"
    }
]

A to následujícím programem:

package main
 
import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
)
 
type User struct {
        Id      uint32
        Name    string
        Surname string
}
 
func main() {
        input_json_as_bytes, err := ioutil.ReadFile("users.json")
        if err != nil {
                log.Fatal(err)
        }
        fmt.Println("Input (bytes):")
        fmt.Println(input_json_as_bytes)
 
        fmt.Println("\nInput (string):")
        fmt.Println(string(input_json_as_bytes))
 
        var users []User
        json.Unmarshal(input_json_as_bytes, &users)
 
        fmt.Println("\nOutput:")
        fmt.Println(users)
 
        fmt.Println("\nUsers:")
        for i, user := range users {
                fmt.Printf("%d\t%d\t%s\t%s\n", i, user.Id, user.Name, user.Surname)
        }
}

Výstup by mohl vypadat takto:

Input (bytes):
[91 10 32 32 32 32 123 10 32 32 32 32 32 32 32 32 34 73 100 34 58 49 44 10 32 32 32 32 32 32 32 32 34 78 97 109 101 34 58 34 80 101 112 101 107 34 44 10 32 32 32 32 32 32 32 32 34 83 117 114 110 97 109 101 34 58 34 86 121 115 107 111 196 141 34 10 32 32 32 32 125 44 10 32 32 32 32 123 10 32 32 32 32 32 32 32 32 34 73 100 34 58 50 44 10 32 32 32 32 32 32 32 32 34 78 97 109 101 34 58 34 80 101 112 101 107 34 44 10 32 32 32 32 32 32 32 32 34 83 117 114 110 97 109 101 34 58 34 86 121 115 107 111 196 141 34 10 32 32 32 32 125 44 10 32 32 32 32 123 10 32 32 32 32 32 32 32 32 34 73 100 34 58 51 44 10 32 32 32 32 32 32 32 32 34 78 97 109 101 34 58 34 74 111 115 101 102 34 44 10 32 32 32 32 32 32 32 32 34 83 117 114 110 97 109 101 34 58 34 86 121 115 107 111 196 141 105 108 34 10 32 32 32 32 125 10 93 10]
 
Input (string):
[
    {
        "Id":1,
        "Name":"Pepek",
        "Surname":"Vyskoč"
    },
    {
        "Id":2,
        "Name":"Pepek",
        "Surname":"Vyskoč"
    },
    {
        "Id":3,
        "Name":"Josef",
        "Surname":"Vyskočil"
    }
]
 
 
Output:
[{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}]
 
Users:
0       1       Pepek   Vyskoč
1       2       Pepek   Vyskoč
2       3       Josef   Vyskočil

13. Import map obsahujících struktury (záznamy)

Ani další konverze (unmarshalling) pravděpodobně nebude nijak překvapivá – načteme JSON obsahující mapu struktur:

{
    "user-id-1": {
                  "Id":1,
                  "Name":"Pepek",
                  "Surname":"Vyskoč"
                 },
     "user-id-2":{
                  "Id":2,
                  "Name":"Josef",
                  "Surname":"Vyskočil"
                 }
}

Do map je samozřejmě možné přidávat další prvky, což za nás opět provede samotná knihovna JSON:

package main
 
import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
)
 
type User struct {
        Id      uint32
        Name    string
        Surname string
}
 
func main() {
        input_json_as_bytes, err := ioutil.ReadFile("users_map.json")
        if err != nil {
                log.Fatal(err)
        }
        fmt.Println("Input (bytes):")
        fmt.Println(input_json_as_bytes)
 
        fmt.Println("\nInput (string):")
        fmt.Println(string(input_json_as_bytes))
 
        m1 := map[string]User{}
        json.Unmarshal(input_json_as_bytes, &m1)
 
        fmt.Println("\nOutput:")
        fmt.Println(m1)
 
        fmt.Println("\nUsers:")
        for key, user := range m1 {
                fmt.Printf("%s\t%d\t%s\t%s\n", key, user.Id, user.Name, user.Surname)
        }
}

14. Specifikace klíčů zapsaných v souborech JSON

Podobně jako při marshallingu je možné i při unmarshallingu specifikovat jména jednotlivých klíčů načítaných struktur (ovšem opět bez kontroly překladačem):

type User struct {
        Id      uint32 `json:"user-id"`
        Name    string `json:"user-name"`
        Surname string
}

Načítat tak budeme moci tento JSON s rozdílnými jmény klíčů:

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

Celý zdrojový kód vypadá takto:

package main
 
import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
)
 
type User struct {
        Id      uint32 `json:"user-id"`
        Name    string `json:"user-name"`
        Surname string
}
 
func main() {
        input_json_as_bytes, err := ioutil.ReadFile("users_map_different_keys.json")
        if err != nil {
                log.Fatal(err)
        }
        fmt.Println("Input (bytes):")
        fmt.Println(input_json_as_bytes)
 
        fmt.Println("\nInput (string):")
        fmt.Println(string(input_json_as_bytes))
 
        m1 := map[string]User{}
        json.Unmarshal(input_json_as_bytes, &m1)
 
        fmt.Println("\nOutput:")
        fmt.Println(m1)
 
        fmt.Println("\nUsers:")
        for key, user := range m1 {
                fmt.Printf("%s\t%d\t%s\t%s\n", key, user.Id, user.Name, user.Surname)
        }
}

15. Načtení předem neznámé struktury z JSONu

Nakonec se podívejme, jak se načítá JSON 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. Ta bude vysvětlena v navazujícím článku.

package main
 
import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
)
 
func main() {
        input_json_as_bytes, err := ioutil.ReadFile("users_map_different_keys.json")
        if err != nil {
                log.Fatal(err)
        }
        fmt.Println("Input (bytes):")
        fmt.Println(input_json_as_bytes)
 
        fmt.Println("\nInput (string):")
        fmt.Println(string(input_json_as_bytes))
 
        m1 := map[string]interface{}{}
        json.Unmarshal(input_json_as_bytes, &m1)
 
        fmt.Println("\nOutput:")
        fmt.Println(m1)
 
        fmt.Println("\nUsers:")
        for key, user := range m1 {
                fmt.Printf("%s\t%s\n", key, user)
        }
}

16. Práce s rastrovými obrázky

V závěrečné části dnešního článku si ukážeme, jakým způsobem je možné vytvářet rastrové obrázky a exportovat je do různých formátů. Zaměříme se přitom na dva nejčastěji používané „webové“ formáty PNG a JPEG. Pro tyto formáty existuje podpora přímo v základní knihovně programovacího jazyka Go, takže není nutné instalovat žádné další balíčky. Samotné rastrové obrázky jsou reprezentovány následujícími datovými typy, jejichž definici najdeme ve standardním balíčku image, například:

Typ Význam
Alpha pixely obsahující pouze alfa složku (průhlednost)
Alpha16 pixely obsahující pouze alfa složku (průhlednost) v šestnáctibitové hloubce
CMYK obrázek používající barvový prostor CMYK
Gray obrázek v odstínech šedi
Gray16 obrázek v odstínech šedi v šestnáctibitové hloubce
YCbCr obrázek používající barvový prostor YCbCr
RGBA obrázek používající barvový prostor RGB s průhledností
RGBA64 obrázek používající barvový prostor RGB s průhledností
Paletted obrázky využívající barvovou paletu

Existují sice i další varianty, ovšem dnes si vystačíme s typy Gray a RGBA.

17. Export rastrového obrazu do PNG

V dalším příkladu nejdříve vytvoříme rastrový obrázek o rozlišení 256×256 pixelů, přičemž bude použit barvový prostor RGBA:

img := image.NewRGBA(image.Rect(0, 0, width, height))

Obrázek vyplníme gradientním přechodem (což nyní není příliš důležité) a následně vytvoříme nový soubor pojmenovaný „test.png“, samozřejmě s kontrolou, zda otevření proběhlo korektně:

outfile, err := os.Create("test.png")
if err != nil {
        panic(err)
}
defer outfile.Close()

Nakonec jednoduše do otevřeného souboru uložíme obrázek ve formátu PNG:

png.Encode(outfile, img)

Výsledek:

Obrázek 1: Obrázek exportovaný demonstračním příkladem.

Zdrojový kód tohoto příkladu vypadá následovně:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewRGBA(image.Rect(0, 0, width, height))
        for x := 0; x < width; x++ {
                for y := 0; y < height; y++ {
                        var red uint8 = uint8(x)
                        var green uint8 = uint8((x + y) >> 1)
                        var blue uint8 = uint8(y)
                        c := color.RGBA{red, green, blue, 255}
                        img.SetRGBA(x, y, c)
                }
        }
 
        outfile, err := os.Create("test.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
        png.Encode(outfile, img)
}

18. Export rastrového obrazu do formátu JPEG

Prakticky stejným způsobem lze provést export do formátu JPEG, pouze se musí nahradit tento řádek:

png.Encode(outfile, img)

za:

jpeg.Encode(outfile, img, nil)

Povšimněte si, že v případě konverze do JPEGu musíme funkci Decode předat ještě jeden parametr, který bude vysvětlen o několik odstavců níže:

package main
 
import (
        "image"
        "image/color"
        "image/jpeg"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewRGBA(image.Rect(0, 0, width, height))
        for x := 0; x < width; x++ {
                for y := 0; y < height; y++ {
                        var red uint8 = uint8(x)
                        var green uint8 = uint8((x - y))
                        var blue uint8 = uint8(y)
                        c := color.RGBA{red, green, blue, 255}
                        img.SetRGBA(x, y, c)
                }
        }
 
        outfile, err := os.Create("test.jpeg")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
        jpeg.Encode(outfile, img, nil)
}

Obrázek 2: Výsledek předchozího příkladu.

Zajímavý je poslední parametr předávaný funkci jpeg.Encode, protože nám umožňuje specifikovat další vlastnosti použité při komprimaci rastrového obrázku do JPEGu. Ovlivnit je možné zejména kvalitu výsledku, takže se podívejme, jak se výsledný obrázek změní ve chvíli, kdy nastavíme nejnižší možnou kvalitu (a tím pádem největší komprimační poměr):

CS24_early

package main
 
import (
        "image"
        "image/color"
        "image/jpeg"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewRGBA(image.Rect(0, 0, width, height))
        for x := 0; x < width; x++ {
                for y := 0; y < height; y++ {
                        var red uint8 = uint8(x)
                        var green uint8 = uint8((x - y))
                        var blue uint8 = uint8(y)
                        c := color.RGBA{red, green, blue, 255}
                        img.SetRGBA(x, y, c)
                }
        }
 
        outfile, err := os.Create("test2.jpeg")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
        jpeg.Encode(outfile, img, &jpeg.Options{Quality: 1})
}

Obrázek 3: Obrázek s nejnižší kvalitou a nejvyšším komprimačním poměrem.

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

Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně jeden megabajt), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Popis Cesta
1 01_json_marshal_basic_signed_types.go marshalling celých čísel se znaménkem do JSONu https://github.com/tisnik/go-fedora/blob/master/article13/01_json_mar­shal_basic_signed_types.go
2 02_json_marshal_basic_unsigned_types.go marshalling celých čísel bez znaménka do JSONu https://github.com/tisnik/go-fedora/blob/master/article13/02_json_mar­shal_basic_unsigned_types­.go
3 03_json_marshal_basic_complex_types.go pokus o marshalling komplexních čísel do JSONu https://github.com/tisnik/go-fedora/blob/master/article13/03_json_mar­shal_basic_complex_types.go
4 04_json_marshal_arrays.go marshalling jednorozměrných i dvourozměrných polí do JSONu https://github.com/tisnik/go-fedora/blob/master/article13/04_json_mar­shal_arrays.go
5 05_json_marshal_struct.go marshalling struktury/záznamu https://github.com/tisnik/go-fedora/blob/master/article13/05_json_mar­shal_struct.go
6 06_json_mashal_array_of_struct.go marshalling pole struktur/záznamů https://github.com/tisnik/go-fedora/blob/master/article13/06_json_mashal_a­rray_of_struct.go
7 07_json_marshal_map_of_structs.go marshalling map struktur/záznamů https://github.com/tisnik/go-fedora/blob/master/article13/07_json_mar­shal_map_of_structs.go
8 08_json_marshal_map_of_structs.go marshalling map struktur/záznamů https://github.com/tisnik/go-fedora/blob/master/article13/08_json_mar­shal_map_of_structs.go
9 09_json_marshal_complex_map.go marshalling map struktur/záznamů https://github.com/tisnik/go-fedora/blob/master/article13/09_json_mar­shal_complex_map.go
10 10_json_different_keys.go změna názvů klíčů https://github.com/tisnik/go-fedora/blob/master/article13/10_json_dif­ferent_keys.go
11 11_json_marshal_special_values.go pokus o marshalling speciálních hodnot https://github.com/tisnik/go-fedora/blob/master/article13/11_json_mar­shal_special_values.go
       
12 12_json_unmarshal_struct.go unmarshalling struktury https://github.com/tisnik/go-fedora/blob/master/article13/12_json_un­marshal_struct.go
13 13_json_unmarshal_struct.go unmarshalling struktury https://github.com/tisnik/go-fedora/blob/master/article13/13_json_un­marshal_struct.go
14 14_json_unmarshal_array.go unmarshalling pole https://github.com/tisnik/go-fedora/blob/master/article13/14_json_un­marshal_array.go
15 15_json_unmarshal_array_of_struct.go unmarshalling pole struktur https://github.com/tisnik/go-fedora/blob/master/article13/15_json_un­marshal_array_of_struct.go
16 16_json_unmarshal_map_of_struct.go unmarshalling mapy struktur https://github.com/tisnik/go-fedora/blob/master/article13/16_json_un­marshal_map_of_struct.go
17 17_json_unmarshal_map_of_struc­t_different_keys.go unmarshalling mapy struktur, specifikace klíčů https://github.com/tisnik/go-fedora/blob/master/article13/17_json_un­marshal_map_of_struct_dif­ferent_keys.go
18 18_json_unmarshal_unknown_struct.go pokus o unmarshalling struktury s obecnými daty https://github.com/tisnik/go-fedora/blob/master/article13/18_json_un­marshal_unknown_struct.go
       
19 19_png_output.go export rastrového obrázku do formátu PNG https://github.com/tisnik/go-fedora/blob/master/article13/19_png_ou­tput.go
20 20_jpeg_output.go export rastrového obrázku do formátu JPEG https://github.com/tisnik/go-fedora/blob/master/article13/20_jpeg_ou­tput.go
21 21_jpeg_output_low_quality.go export rastrového obrázku do formátu JPEG s ovlivněním kvality https://github.com/tisnik/go-fedora/blob/master/article13/21_jpeg_ou­tput_low_quality.go

20. Odkazy na Internetu

  1. YAML
    https://yaml.org/
  2. edn
    https://github.com/edn-format/edn
  3. Smile
    https://github.com/FasterXML/smile-format-specification
  4. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  5. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  6. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  7. Introducing JSON
    http://json.org/
  8. Package json
    https://golang.org/pkg/encoding/json/
  9. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  10. Go by Example: JSON
    https://gobyexample.com/json
  11. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  12. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  13. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  14. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  15. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  16. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  17. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  18. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  19. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  20. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  21. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  22. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  23. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  24. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  25. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  26. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  27. Algorithms to Go
    https://yourbasic.org/
  28. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  29. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/
  30. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  31. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  32. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  33. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  34. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  35. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  36. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  37. The Go Programming Language (home page)
    https://golang.org/
  38. GoDoc
    https://godoc.org/
  39. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  40. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  41. The Go Programming Language Specification
    https://golang.org/ref/spec
  42. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  43. Package builtin
    https://golang.org/pkg/builtin/
  44. Package fmt
    https://golang.org/pkg/fmt/
  45. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  46. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  47. Learning Go
    https://www.miek.nl/go/
  48. Go Bootcamp
    http://www.golangbootcamp.com/
  49. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  50. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  51. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  52. The Go Blog
    https://blog.golang.org/
  53. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  54. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  55. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  56. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  57. How the Go runtime implements maps efficiently (without generics)
    https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics
  58. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  59. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  60. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  61. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  62. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  63. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  64. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  65. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  66. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  67. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  68. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  69. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  70. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  71. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  72. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  73. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  74. Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
    https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/
  75. 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
    https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd
  76. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  77. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  78. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  79. Go vs. Python
    https://www.peterbe.com/plog/govspy
  80. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  81. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  82. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  83. Go by Example: Slices
    https://gobyexample.com/slices
  84. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  85. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  86. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  87. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  88. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  89. nils In Go
    https://go101.org/article/nil.html
  90. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  91. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  92. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  93. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  94. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  95. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  96. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  97. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  98. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  99. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  100. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  101. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  102. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  103. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  104. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  105. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  106. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  107. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  108. Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
    https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06
  109. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  110. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  111. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  112. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  113. Selectors
    https://golang.org/ref/spec#Selectors
  114. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  115. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  116. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  117. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  118. Part 21: Goroutines
    https://golangbot.com/goroutines/
  119. Part 22: Channels
    https://golangbot.com/channels/
  120. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  121. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  122. Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
    https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/
  123. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  124. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  125. Control Structures
    https://www.golang-book.com/books/intro/5
  126. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  127. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  128. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  129. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  130. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  131. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  132. Effective Go
    https://golang.org/doc/ef­fective_go.html
  133. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  134. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation

Byl pro vás článek přínosný?

Autor článku

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