XML a JSON v Go

Standardní knihovna Go mapuje XML a JSON do struktur a umí streamově dekódovat. Neposkytuje kompletní DOM, XPath ani XSD validátor.

Autor: Aktualizováno

1. Načtení XML

Encoding/xml nabízí Unmarshal pro byte slice a Decoder pro io.Reader. Exportovaná pole a tagy struktury určují mapování. Následující úplný program zároveň vytvoří a serializuje XML a JSON; ulož jej jako main.go a spusť go run main.go.

1. Načtení XML
package main

import (
 "encoding/xml"
 "encoding/json"
 "fmt"
)

type Kniha struct {
 XMLName xml.Name `xml:"kniha" json:"-"`
 ID string `xml:"id,attr" json:"id"`
 Nazev string `xml:"nazev" json:"nazev"`
}
func main() {
 var k Kniha
 if err := xml.Unmarshal([]byte(`<kniha id="1"><nazev>XML</nazev></kniha>`), &k); err != nil { panic(err) }
 k.Nazev = "XML & JSON"
 x, err := xml.Marshal(k); if err != nil { panic(err) }
 fmt.Println(string(x))
 j, err := json.Marshal(k); if err != nil { panic(err) }
 fmt.Println(string(j))
 var copy Kniha
 if err := json.Unmarshal(j, &copy); err != nil { panic(err) }
 fmt.Println(copy.ID, copy.Nazev)
}

2. Vytvoření XML

Naplň strukturu Kniha nebo použij Encoder s tokeny. Namespace může být součástí xml tagu struktury. Mapování struktur není obecně bezeztrátová archivace libovolného smíšeného obsahu.

3. Serializace XML

Marshal a Encoder.Encode escapují text a atributy. MarshalIndent přidá odsazení. XMLName v ukázce určuje kořen a json tag jej vyřazuje z JSON reprezentace.

4. DOM

Encoding/xml je tokenový parser a mapování struktur, ne DOM API. Pokud potřebuješ libovolnou navigaci a úpravy stromu, použij vhodnou knihovnu a prověř její namespace model.

5. Streamové čtení

Decoder.Token vrací tokeny; DecodeElement načte vybraný element do struktury. Ukončení io.EOF odliš od chyby. Nezadržuj všechny dekódované záznamy ve slice, pokud chceš omezenou paměť.

6. XPath

Ve standardní knihovně XPath není. Pro známé struktury často stačí průchod tokeny. Knihovnu XPath vybírej podle verze a namespace podpory, nikoli podle názvu balíčku.

7. Validace XSD

Unmarshal neověří všechna pravidla XSD. Nepřítomná pole se mohou stát nulovými hodnotami. Použij samostatný validátor, pokud příjemce požaduje shodu s XSD.

8. Bezpečnost parseru

Decoder nevytváří síťový resolver DTD jako běžný validující XML procesor. Přesto omez vstup a hloubku. Strict nevynucuje všechna namespace pravidla; dokumentace výslovně upozorňuje na chování nedefinovaných prefixů.

9. Načtení JSON

Unmarshal do struktury je pohodlné; pro čtení z proudu použij json.Decoder. UseNumber zachová čísla v interface jako json.Number místo float64. DisallowUnknownFields zpřísní struktury, ale není kompletním JSON Schema.

10. Vytvoření JSON

Vytvoř strukturu nebo mapu se známými poli. Rozlišuj prázdné pole a nil slice podle požadované serializace. Zkontroluj omitempty, pokud je nulová hodnota významová; podrobně v sekci struct tagy a chybějící hodnoty.

11. Serializace JSON

Marshal nebo Encoder.Encode vytvoří JSON; kontroluj chybu, například pro NaN a Infinity. Encoding/json escapuje některé HTML znaky, což se může projevit v textu ukázky bez změny načtené hodnoty.

12. JSON Schema

Go standardní knihovna validátor neobsahuje. Použij knihovnu s explicitní podporou požadovaného draftu; ověř vzdálené reference i chování format. Runtime schéma doplňuje typy struktur, nenahrazuje je.

Primární dokumentace: encoding/xml a encoding/json.

Stream: výběr knih podle namespace

stream.go — spusť jako samostatný program go run stream.go
package main
import ("encoding/xml"; "fmt"; "io"; "os")
type Kniha struct {
 XMLName xml.Name `xml:"urn:knihy kniha"`
 ID string `xml:"id,attr"`
 Nazev string `xml:"urn:knihy nazev"`
}
func main() {
 f, err := os.Open("katalog.xml"); if err != nil { panic(err) }; defer f.Close()
 info, err := f.Stat(); if err != nil { panic(err) }
 if info.Size() > 10*1024*1024 { panic("Limit souboru") }
 decoder := xml.NewDecoder(f)
 for {
  token, err := decoder.Token()
  if err == io.EOF { break }; if err != nil { panic(err) }
  start, ok := token.(xml.StartElement)
  if !ok || start.Name.Space != "urn:knihy" || start.Name.Local != "kniha" { continue }
  var k Kniha
  if err := decoder.DecodeElement(&k, &start); err != nil { panic(err) }
  fmt.Println(k.ID, k.Nazev)
 }
}
katalog.xml pro stream.go
<katalog xmlns="urn:knihy"><kniha id="k1"><nazev>XML</nazev></kniha><kniha id="k2"><nazev>XPath</nazev></kniha></katalog>

Výstup jsou řádky k1 XML a k2 XPath. Decoder čte postupně a každou knihu načte až při jejím startu. Kontrola namespace zabrání záměně s cizím elementem kniha. Po zpracování strukturu nezadržuj v rostoucím seznamu. Limit velikosti je zde kontrola známého lokálního souboru; pro příchozí stream musíš limitovat skutečné čtení a ošetřit truncation i hloubku.

XMLName a tagy struktur vyjadřují mapování, ne všechny podmínky XSD. Například chybějící Nazev může skončit jako prázdný string. Pro DOM a XPath lze zvolit xmlquery s vlastním stromovým modelem; pro XSD samostatný libxml2 procesor, například xmllint, nebo udržovaný binding. Ověř verzi i zabezpečení samostatné validační fáze.

JSON Schema v knihovně v6

schema.go — samostatný Go modul s github.com/santhosh-tekuri/jsonschema/v6
package main
import (
 "fmt"
 "strings"
 "github.com/santhosh-tekuri/jsonschema/v6"
)
func main() {
 schema, err := jsonschema.UnmarshalJSON(strings.NewReader(`{
  "$schema":"https://json-schema.org/draft/2020-12/schema",
  "type":"object", "required":["id"],
  "properties":{"id":{"type":"string"}}, "additionalProperties":false
 }`)); if err != nil { panic(err) }
 c := jsonschema.NewCompiler()
 if err := c.AddResource("https://example.cz/schema/kniha", schema); err != nil { panic(err) }
 compiled, err := c.Compile("https://example.cz/schema/kniha"); if err != nil { panic(err) }
 for _, raw := range []string{`{"id":"001"}`, `{"id":1}`} {
  data, err := jsonschema.UnmarshalJSON(strings.NewReader(raw)); if err != nil { panic(err) }
  err = compiled.Validate(data)
  fmt.Println(err == nil)
 }
}

Ve vlastní prázdné složce spusť go mod init ukazka, go get github.com/santhosh-tekuri/jsonschema/v6@v6.0.3 a go run schema.go. Výsledek je true a false. Schéma je registrováno lokálně pod URI a nevyžaduje stahovat example.cz. UnmarshalJSON je zde z knihovny validátoru, která používá vhodnou reprezentaci čísel. Tento krok je jiný než Go deserializace do struktury.

Neznámá pole u Go struktur může zpřísnit DisallowUnknownFields, ale neověří enum, pattern či logické kombinace schématu. U více JSON textů kontroluj po první hodnotě konec vstupu. API v6: oficiální dokumentace.

Struct tagy a chybějící hodnoty

Go nemá pro pole struktury stav „nenastaveno“. Když klíč ve vstupu chybí, zůstane v poli nulová hodnota typu: 0, "", false. Podle hodnoty 0 pak nepoznáš, jestli klient poslal nulu, nebo nic. Pole s ukazatelem (*int) tento rozdíl zachová: nil znamená, že hodnota nepřišla.

main.go — Go 1.25, go run main.go
package main

import (
  "encoding/json"
  "encoding/xml"
  "fmt"
  "time"
)

type Produkt struct {
  XMLName xml.Name  `xml:"produkt" json:"-"`
  Kod     string    `xml:"kod,attr" json:"kod"`
  Nazev   string    `xml:"nazev" json:"nazev"`
  Popis   string    `xml:",chardata" json:"-"`
  Sleva   int       `xml:"sleva,omitempty" json:"sleva,omitempty"`
  Sklad   *int      `xml:"sklad,omitempty" json:"sklad,omitempty"`
  Stitky  []string  `xml:"stitek" json:"stitky,omitempty"`
  Zmeneno time.Time `xml:"-" json:"zmeneno,omitzero"`
}

func main() {
  for _, vstup := range []string{
    `{"kod":"A1","nazev":"Hrnek"}`,
    `{"kod":"A1","nazev":"Hrnek","sklad":0,"sleva":0}`,
    `{"kod":"A1","nazev":"Hrnek","sklad":null}`,
  } {
    var p Produkt
    if err := json.Unmarshal([]byte(vstup), &p); err != nil {
      panic(err)
    }
    if p.Sklad == nil {
      fmt.Print("sklad: chybí nebo null")
    } else {
      fmt.Printf("sklad: %d", *p.Sklad)
    }
    fmt.Printf(", sleva: %d\n", p.Sleva)
  }

  nula := 0
  p := Produkt{Kod: "A1", Nazev: "Hrnek", Sklad: &nula}
  j, _ := json.Marshal(p)
  fmt.Println(string(j))
  p.Zmeneno = time.Date(2026, 10, 4, 12, 0, 0, 0, time.UTC)
  p.Stitky = []string{"keramika", "výprodej"}
  p.Sleva = 10
  j, _ = json.Marshal(p)
  fmt.Println(string(j))
  x, _ := xml.MarshalIndent(p, "", "  ")
  fmt.Println(string(x))


  var z Produkt
  err := xml.Unmarshal([]byte(`<produkt kod="B2"><nazev>Talíř</nazev>bílý<sklad>3</sklad></produkt>`), &z)
  fmt.Println(err, z.Kod, z.Nazev, z.Popis, *z.Sklad)
}
Výstup
sklad: chybí nebo null, sleva: 0
sklad: 0, sleva: 0
sklad: chybí nebo null, sleva: 0
{"kod":"A1","nazev":"Hrnek","sklad":0}
{"kod":"A1","nazev":"Hrnek","sleva":10,"sklad":0,"stitky":["keramika","výprodej"],"zmeneno":"2026-10-04T12:00:00Z"}
<produkt kod="A1">
  <nazev>Hrnek</nazev>
  <sleva>10</sleva>
  <sklad>0</sklad>
  <stitek>keramika</stitek>
  <stitek>výprodej</stitek>
</produkt>
<nil> B2 Talíř bílý 3
  • Ukazatel. Sleva je int, takže chybějící klíč i "sleva":0 dají 0. Sklad je *int a rozliší chybějící hodnotu od nuly. Klíč s hodnotou null dá stejně jako chybějící klíč nil.
  • omitempty vynechá při zápisu false, 0, nil ukazatel a prázdný řetězec, řez nebo mapu. Ukazatel na nulu není prázdný, proto se "sklad":0 zapíše. Na struktury omitempty nepůsobí: prázdný time.Time by se zapsal jako "0001-01-01T00:00:00Z".
  • omitzero (od Go 1.24) vynechá nulovou hodnotu libovolného typu. Pokud typ má metodu IsZero(), použije ji, takže funguje i pro time.Time.
  • Tagy xml. ,attr zapíše pole jako atribut, ,chardata zachytí text elementu (zde „bílý“ mezi elementy). Řez bez obalového elementu dá opakované elementy stitek. Pomlčka - pole v daném formátu vynechá.
  • Řez nil a prázdný řez. Bez omitempty se řez nil zapíše jako null, prázdný řez jako []. Pokud klient očekává vždy pole, inicializuj řez.

Když potřebuješ rozlišit všechny tři stavy (klíč chybí, null, hodnota), typicky u částečné aktualizace podle JSON Merge Patch, ukazatel nestačí. Pomůže vlastní typ s metodou UnmarshalJSON, kterou dekodér zavolá jen pro klíče přítomné ve vstupu:

Tři stavy hodnoty
package main

import (
  "encoding/json"
  "fmt"
)

// Volitelna rozlišuje chybějící klíč, null a hodnotu.
type Volitelna[T any] struct {
  Zadano bool // klíč byl ve vstupu
  Null   bool
  Hodnota T
}

func (v *Volitelna[T]) UnmarshalJSON(b []byte) error {
  v.Zadano = true
  if string(b) == "null" {
    v.Null = true
    return nil
  }
  return json.Unmarshal(b, &v.Hodnota)
}

type Zmena struct {
  Sklad Volitelna[int] `json:"sklad"`
}

func main() {
  for _, vstup := range []string{`{}`, `{"sklad":null}`, `{"sklad":0}`} {
    var z Zmena
    if err := json.Unmarshal([]byte(vstup), &z); err != nil { panic(err) }
    fmt.Printf("%-15s zadáno=%v null=%v hodnota=%d\n", vstup, z.Sklad.Zadano, z.Sklad.Null, z.Sklad.Hodnota)
  }
}
Výstup
{}              zadáno=false null=false hodnota=0
{"sklad":null}  zadáno=true null=true hodnota=0
{"sklad":0}     zadáno=true null=false hodnota=0

Stejný rozdíl mezi chybějícím klíčem, JSON null a SQL NULL řeší i PostgreSQL. Zdroj: encoding/json Marshal a Go 1.24 release notes.