Serializarea JSON cu Golang

Prezentare generală

JSON este unul dintre cele mai populare formate de serializare. Este citit de om, rezonabil rezonabil și poate fi analizat ușor de orice aplicație web care utilizează JavaScript. Du-te ca un limbaj de programare modern are suport de primă clasă pentru serializarea JSON în biblioteca standard. 

Dar există unele nooks și crennies. În acest tutorial veți învăța cum să serializați și să deserializați date arbitrare și structurate în / de la JSON. Veți învăța, de asemenea, cum să vă ocupați de scenarii avansate, cum ar fi enumerările serializării.

Pachetul json

Go acceptă mai multe formate de serializare în pachetul de codare al bibliotecii sale standard. Unul dintre acestea este formatul popular JSON. Serializați valorile Golang folosind funcția Marshal () într-o felie de octeți. Puteți deserializa o felie de octeți într-o valoare Golang utilizând funcția Unmarshal (). Este atat de simplu. Următorii termeni sunt echivalenți în contextul prezentului articol:

  • Serializare / Codare / marshalling
  • Deserializarea / Decodare / Unmarshalling

Prefer serializarea pentru că reflectă faptul că convertiți o structură de date ierarhică potențial la / de la un flux de octeți.

mareșal

Funcția Marshal () poate lua orice, care în Go înseamnă interfața goală și returnează o felie de octeți și eroare. Iată semnătura:

func Marshal (v interfață ) ([] octet, eroare)

Dacă Marshal () nu reușește să serializeze valoarea de intrare, va reveni la o eroare non-zero. Mareșalul () are câteva limitări stricte (vom vedea mai târziu cum să le depășim cu marshallers personalizați):

  • Tastele de hartă trebuie să fie șiruri de caractere.
  • Valorile hărții trebuie să fie serializabile prin pachetul json.
  • Nu sunt acceptate următoarele tipuri: canal, complex și funcție.
  • Structurile de date ciclice nu sunt acceptate.
  • Pointerii vor fi codificați (și mai apoi decodați) ca valorile pe care le indică (sau 'null' dacă indicatorul este zero).

Unmarshal

Funcția Unmarshal () are o secvență de octeți care, sperăm, reprezintă o interfață JSON validă și o interfață de destinație, care este de obicei un pointer la un tip struct sau de bază. Acesta deserializează JSON-ul în interfață într-un mod generic. Dacă serializarea nu a reușit, aceasta va afișa o eroare. Iată semnătura:

func Unmarshal (date [] octet, v interfață ) eroare

Serializarea tipurilor simple

Aveți posibilitatea să serializați cu ușurință tipuri simple, cum ar fi utilizarea pachetului json. Rezultatul nu va fi un obiect JSON cu drepturi depline, ci un simplu șir. Aici int 5 este serializat la matricea de octeți [53], care corespunde șirului "5".

 Serializați int var x = 5 octeți, err: = json.Marshal (x) dacă err! = Nil fmt.Println ("Nu se poate serisliza", x) fmt.Printf ("% v =>% v , '% v' \ n ", x, octeți, șir (octeți)) // Deserialize int var r int err = json.Unmarshal (octeți, & r) err! = nil fmt.Println ", bytes) fmt.Printf ("% v =>% v \ n ", octeți, r) Ieșire: - 5 => [53], 5

Dacă încercați să serializați tipuri neacceptate ca o funcție, veți primi o eroare:

 // Încercarea de a serializa o funcție foo: = func () fmt.Println ("foo () aici") octeți, err = json.Marshal (foo) dacă err! = Nil fmt.Println (err) : json: tipul neacceptat: func ()

Serializarea datelor arbitrare cu ajutorul hărților

Puterea JSON este că poate reprezenta foarte bine date ierarhice arbitrare. Pachetul JSON îl acceptă și utilizează interfața generică goală (interfața ) pentru a reprezenta orice ierarhie JSON. Iată un exemplu de deserializare și mai târziu serializarea unui arbore binar în care fiecare nod are o valoare int și două ramuri, stânga și dreapta, care pot conține un alt nod sau pot fi nul.

Nulul JSON este echivalent cu Go nil. După cum puteți vedea în ieșire, json.Unmarshal () funcția a transformat cu succes jetonul JSON într-o structură de date Go, formată dintr-o hartă imbricată de interfețe și a păstrat tipul de valoare ca int. json.Marshal () funcția a serializat cu succes obiectul imbricat rezultat la aceeași reprezentare JSON.

 // JSON imbricată arbitrar dd: = '"valoare: 3," stânga ": " valoare ": 1," stânga ": null, "dreapta": null, "right": "value": 4, "left": null, , & obj) dacă err! = nil fmt.Println (err) altceva fmt.Println ("-------- \ n", obj) date err = json.Marshal ! = nil fmt.Println (err) altceva fmt.Println ("-------- \ n", șir (date)) Output: -------- map [ : harta [valoare: 4 stânga: dreapta:] valoare: 3 stânga: harta [stânga: dreapta: harta [valoare: 2 stânga: dreapta:]] -------- "stânga": "stânga": null, "dreapta": "stânga": nulă, "dreapta" "valoarea": ​​1, "dreapta": "stânga": nulă, "dreapta": nulă, "valoare": 4 

Pentru a traversa hărțile generice ale interfețelor, va trebui să utilizați aserțiuni de tip. De exemplu:

functie dump obj interfață ) if obj == nil fmt.Println ("nil") return nil comutator volum (tip) caz bool: fmt.Println (obj. fmt.Println (volum (int)) caz float64: fmt.Println (volum (float64)) string string: fmt.Println  = eroare: (hartă (șir) interfață )) fmt.Printf ("% s:", k) err: = dump (v) Nou (fmt.Sprintf ("Tip neacceptat:% v", obj)) retur nul

Serializarea datelor structurate

Lucrul cu datele structurate este adesea cea mai bună alegere. Go oferă un suport excelent pentru serializarea JSON la / de la structs prin intermediul lui struct Etichete. Să creăm a struct care corespunde arborelui nostru JSON și mai inteligent Dump () funcția care o imprimă:

Tipul structului valoare int stânga * Arbore dreapta * Arbore func (t * Arbore) Dump (liniuță) fmt.Println (indent + "value:", t.value) ) dacă t.left == nu fmt.Println (nil) altceva fmt.Println () t.left.Dump (indent + "") fmt.Print (indent + "right:") == nil fmt.Println (nil) altceva fmt.Println () t.right.Dump (indent + "") 

Acest lucru este minunat și mult mai curat decât abordarea arbitrară JSON. Dar funcționează? Nu chiar. Nu există nici o eroare, dar obiectul nostru arbore nu este populat de JSON.

 jsonTree: = '"valoare": 3, "stânga": "valoare": 1, "stânga": null, "right": null , "dreapta": "valoare": 4, "stânga": null, "dreapta": null var tree copac err = json.Unmarshal ([ nil fmt.Printf ("- Nu pot desertifica copacul, eroare:% v \ n", err) altceva tree.Dump ("") Output:  dreapta:  

Problema este că câmpurile de copaci sunt private. Serializarea JSON funcționează numai pe câmpurile publice. Așa că putem face struct domenii publice. Pachetul json este suficient de inteligent pentru a converti în mod transparent tastele "valoare", "stânga" și "dreapta" cu majusculele lor corespunzătoare majuscule.

Tipul structurii Value int 'json: "value"' Left * Tree 'json: "stânga"  dreapta: valoare: 2 stânga:  dreapta:  dreapta: valoare: 4 stânga:  dreapta:  

Pachetul json va ignora în tăcere câmpurile neimprimate în câmpurile JSON, precum și în câmpurile private din struct. Dar, uneori, poate doriți să mapați cheile specifice din JSON într-un câmp cu un nume diferit în dvs. struct. Poți să folosești struct etichete pentru asta. De exemplu, să presupunem că adăugăm un alt câmp numit "label" la JSON, dar trebuie să-l mapăm într-un câmp numit "Tag" în structura noastră. 

tip Tree struct int Valoare int String de caractere 'json: "label"' Stânga * Arbore dreapta * Arbore func (t * Arbore) Dump (indent indent) fmt.Println (indent + "value:", t.Value) t.Tag! = "" fmt.Println (indent + "tag:", t.Tag) fmt.Print (linia + "stânga") dacă t.Left == zero fmt.Println (nil) altfel fmt.Println () t.Left.Dump (indent + "") fmt.Print (indent + "right:") dacă t.Right == nu fmt.Println (nil) () t.Right.Dump (linia + "") 

Aici este noul JSON cu nodul rădăcină al arborelui etichetat ca "root", serializat corespunzător în câmpul Tag și tipărit în ieșire:

 dd: = '"etichetă": "rădăcină", ​​"valoare": 3, "stânga": "valoare": 1, "stânga" : null, "right": "value": 4, "left": null, "right": null var copac err = json.Unmarshal ), & tree) dacă err! = nil fmt.Printf ("- Nu poate desertifica copacul, eroare:% v \ n", err) rădăcina stânga: valoare: 1 stânga:  dreapta: valoare: 2 stânga:  dreapta:  dreapta: valoare: 4 stânga:  dreapta: 

Scrierea unui marșaller personalizat

Veți dori adesea să serializați obiecte care nu se conformează cerințelor stricte ale funcției Marshal (). De exemplu, poate doriți să serializați o hartă cu tastele int. În aceste cazuri, puteți scrie un custom marshaller / unmarshaller prin implementarea Marshaler și Unmarshaler interfețe.

O notă despre ortografie: În Go, convenția este de a numi o interfață cu o singură metodă prin adăugarea sufixului "er" la numele metodei. Deci, chiar dacă ortografia mai frecventă este "Marshaller" (cu dublu L), numele interfeței este doar "Marshaler" (single L).

Aici sunt interfețele Marshaler și Unmarshaler:

tip Marshaler interfață MarshalJSON () ([] octet, eroare) tip interfață Unmarshaler UnmarshalJSON ([] octet) eroare 

Trebuie să creați un tip atunci când efectuați serializarea personalizată, chiar dacă doriți să serializați un tip sau o compoziție încorporată a tipurilor încorporate harta [int] string. Aici definesc un tip numit IntStringMap și să pună în aplicare Marshaler și Unmarshaler interfețe pentru acest tip.

MarshalJSON () metoda creează o harta [string] string, convertește fiecare dintre propriile sale taste int la un șir și serializează harta cu chei de șir folosind standardul json.Marshal () funcţie.

tip IntStringMap hartă [int] șir func (m * IntStringMap) MarshalJSON () ([] octet, eroare) ss: = map [string] string  pentru k, v: = range * m i: = strconv.Itoa (k) ss [i] = v returnează json.Marshal (ss) 

Metoda UnmarshalJSON () face exact opusul. Acesta deserializează matricea de octeți de date în a harta [string] string și apoi convertește fiecare cheie de șir într-un int și se populaie singură.

(eroare!) = eror: = json.Unmarshal (date, & ss) dacă err! = nil return err pentru k, v: = intervalul ss i, err: = strconv.Atoi (k) dacă err! = nil return err (* m) [i] = v 

Iată cum să le folosiți într-un program:

 m: IntStringMap 4: "patru", 5: "cinci" date, err: = m.MarshalJSON () err! = nil fmt.Println (err) fmt.Println ("IntStringMap to JSON: , șir (date)) m = IntStringMap  jsonString: = [] octet ("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" ), m [1], m [1], "m [2]:", m [2]) Ieșire : IntStringMap către JSON: "4": "four", "5": "five" IntStringMap din JSON: map [2: 2 1: 1]

Serializând Enums

Du-te enums poate fi destul de vexing pentru a serializa. Ideea de a scrie un articol despre serializarea Go jon a ieșit dintr-o întrebare pe care un coleg mi-a cerut-o despre cum să serializăm enum-urile. Iată un du-te enum. Constantele zero și unu sunt egale cu inturile 0 și 1.

tip EnumType int const (Zero EnumType = iota One) 

În timp ce ați putea crede că este un int, și în multe privințe este, nu îl puteți serializa direct. Trebuie să scrieți un marshaler personalizat / unmarshaler. Nu este o problemă după ultima secțiune. Următoarele MarshalJSON () și UnmarshalJSON () va serializa / deserializa constantele ZERO și ONE în / de la șirurile corespunzătoare "Zero" și "One".

(eroare) err! = nil return err valoare, ok: = harta [șir] EnumType " Zero ": Zero," One ": Unul [s] dacă! OK returnează erorile.New (" Valoarea Invalid EnumType ") , eroare) value, ok: = hartă [EnumType] șir Zero: "Zero", One: "One" json.Marshal (valoare) 

Să încercăm să încorporăm asta EnumType în a struct și serializează-o. Funcția principală creează o EnumContainer și o inițiază cu un nume de "Uno" și o valoare a noastră enum constant UNU, care este egal cu int1.

tip EnumContainer struct Numele șir Valoare EnumType func principal () x: = Un ec: = EnumContainer "Uno", x, s, err: = json.Marshal (ec) în cazul err! = nil fmt.Printf ("eșuează") var ec2 EnumContainer err = json.Unmarshal (s, & ec2) fmt.Println (ec2.Name, ":", ec2.Value) 

Ieșirea așteptată este "Uno: 1", dar în schimb este "Uno: 0". Ce s-a întâmplat? Nu există niciun bug în codul marshal / unmarshal. Se pare că nu puteți încorpora enums prin valoare dacă doriți să le serializați. Trebuie să încorporați un pointer în enum. Aici este o versiune modificată în cazul în care funcționează după cum era de așteptat:

tip EnumContainer struct Numele șir Valoare * EnumType func principal () x: = Un ec: = EnumContainer "Uno", & x, s, err: = json.Marshal (ec) în cazul err! = nil fmt. Printr ("nu!") Var ec2 EnumContainer err = json.Unmarshal (s, & ec2) fmt.Println (ec2.Name, ":", * ec2.Value)

Concluzie

Go oferă multe opțiuni pentru serializarea și deserializarea JSON. Este important să înțelegeți punctele de intrare și ieșire din pachetul de codare / json pentru a profita de puterea.

Acest tutorial pune toată puterea în mâinile tale, incluzând și modul de serializare a enumelor goale.

Serializați câteva obiecte!

Cod