JSON Serialization With Golang

Oversikt

JSON er et av de mest populære serialiseringsformatene. Det er menneskelig lesbar, rimelig konsistent, og kan analyseres enkelt av en hvilken som helst webapplikasjon ved hjelp av JavaScript. Gå som et moderne programmeringsspråk har førsteklasses støtte for JSON serialisering i sitt standardbibliotek. 

Men det er noen kroker og kroker. I denne opplæringen lærer du hvordan du effektivt serialiserer og deserialiserer vilkårlig samt strukturert data til / fra JSON. Du vil også lære hvordan du skal håndtere avanserte scenarier som serielliseringer.

Json-pakken

Go støtter flere serialiseringsformater i kodingspakken i standardbiblioteket. En av disse er det populære JSON-formatet. Du serialiserer Golang-verdier ved hjelp av Marshal () -funksjonen i et stykke byte. Du deserialiserer et stykke byte til en Golang-verdi ved hjelp av Unmarshal () -funksjonen. Det er så enkelt. Følgende vilkår er ekvivalente i sammenheng med denne artikkelen:

  • Serialisering / koding / ranger
  • Deserialization / dekoding / Unmarshalling

Jeg foretrekker serialisering fordi den gjenspeiler det faktum at du konverterer en potensielt hierarkisk datastruktur til / fra en strøm av byte.

Marshal

Marshal () -funksjonen kan ta noe, som i Go betyr det tomme grensesnittet og returnerer et stykke byte og feil. Her er signaturen:

func Marshal (v grensesnitt ) ([] byte, feil)

Hvis Marshal () ikke serialiserer inngangsverdien, returnerer den en ikke-null feil. Marshal () har noen strenge begrensninger (vi vil se senere hvordan å overvinne dem med tilpassede marshallere):

  • Kartnøkler må være strenger.
  • Kartverdier må være serialiserbare i json-pakken.
  • Følgende typer støttes ikke: Kanal, kompleks og funksjon.
  • Sykliske datastrukturer støttes ikke.
  • Pekere blir kodet (og senere dekodet) som verdiene de peker på (eller 'null' hvis pekeren er null).

Unmarshal

Funksjonen Unmarshal () tar et bytesnitt som forhåpentligvis representerer gyldig JSON og et destinasjonsgrensesnitt, som vanligvis er en peker til en struktur eller grunnleggende type. Det deserialiserer JSON i grensesnittet på en generisk måte. Hvis serialiseringen mislyktes, returnerer den en feil. Her er signaturen:

func Unmarshal (data [] byte, v grensesnitt ) feil

Serialiserende enkle typer

Du kan enkelt serialisere enkle typer som å bruke json-pakken. Resultatet blir ikke et fullverdig JSON-objekt, men en enkel streng. Her er int 5 serialisert til byte-arrayet [53], som tilsvarer strengen "5".

 // Serialize int var x = 5 bytes, err: = json.Marshal (x) hvis feil! = Null fmt.Println ("Kan ikke serislize", x) fmt.Printf ("% v =>% v , '% v' \ n ", x, byte, streng (byte)) // Deserialize int var r int err = json.Unmarshal (bytes, & r) hvis feil! = null fmt.Println (" Kan ikke deserislize "byte" fmt.Printf ("% v =>% v \ n", byte, r) Utgang: - 5 => [53], '5' - [53] => 5

Hvis du prøver å serialisere ikke-støttede typer som en funksjon, får du en feil:

 // Prøv å serialisere en funksjon foo: = func () fmt.Println ("foo () her") bytes, err = json.Marshal (foo) hvis feil! = Null fmt.Println (err) Utgang : json: unsupported type: func ()

Serialiserer vilkårlig data med kart

Kraften til JSON er at den kan representere vilkårlig hierarkiske data veldig bra. JSON-pakken støtter det og bruker det generiske tomme grensesnittet (grensesnitt ) for å representere ethvert JSON-hierarki. Her er et eksempel på deserialisering og senere serialisering av et binært tre hvor hver node har en int verdi og to grener, venstre og høyre, som kan inneholde en annen knutepunkt eller være null.

JSON null svarer til Go nil. Som du kan se i utgangen, er json.Unmarshal () funksjonen har konvertert JSON-bloben til en Go-datastruktur bestående av et innbundet kart over grensesnitt og bevaret verdi typen som int. De json.Marshal () funksjonen serialisert suksessielt den resulterende nestede gjenstanden til den samme JSON-representasjonen.

 // Vilkårlig nestet JSON dd: = '"verdi": 3, "venstre": "verdi": 1, "venstre": null, "høyre": "verdi": 2, "venstre" "høyre": null, "høyre": "verdi": 4, "venstre": null, "høyre": null 'var obj interface  err = json.Unmarshal ([] byte , og obj) hvis feil! = null fmt.Println (err) else fmt.Println ("-------- \ n", obj) data, err = json.Marshal (obj) hvis feil ! = nil fmt.Println (err) else fmt.Println ("-------- \ n", streng (data)) Utgang: -------- kart [høyre : kart [verdi: 4 venstre: Ikke sant:] verdi: 3 igjen: kart [venstre: høyre: kart [verdi: 2 igjen: Ikke sant:] -------- "left": "left": null, "right": "left": null, "høyre": null, "verdi": 2, "verdi": 1, "høyre": "venstre": null, "høyre": null, "verdi": 4, "verdi": 3 

For å krysse generiske kart over grensesnitt, må du bruke type påstander. For eksempel:

funk dump (obj interface ) feil hvis obj == nil fmt.Println ("nil") returnere null bytt obj. (type) case bool: fmt.Println (obj. fmt.Println (obj. (int)) tilfelle float64: fmt.Println (obj. (float64)) saksstreng: fmt.Println (obj. (streng)) case map [streng] grensesnitt : for k, v: = rekkevidde (obj. (kart [streng] grensesnitt )) fmt.Printf ("% s:", k) err: = dump (v) hvis err! = null return err standard: returfeil. Nytt (fmt.Sprintf ("Ikke støttet type:% v", obj)) returnere null

Serialiserende strukturert data

Å jobbe med strukturert data er ofte det bedre valget. Go gir utmerket støtte for serialisering av JSON til / fra structs via sin struct tags. La oss lage en struct som tilsvarer vårt JSON-tre og en smartere Dump () funksjon som skriver den ut:

Skriv inn Tree struktur verdi int venstre * Tree høyre * Tree func (t * Tree) Dump (strekkstreng) fmt.Println (indent + "verdi:", t.value) fmt.Print (indent + "left:" ) hvis t.left == nil fmt.Println (nil) else fmt.Println () t.left.Dump (indent + "" fmt.Print (indent + "right:") hvis t.right == nil fmt.Println (nil) else fmt.Println () t.right.Dump (indent + "") 

Dette er flott og mye renere enn den vilkårlig JSON-tilnærmingen. Men fungerer det? Ikke egentlig. Det er ingen feil, men vårt treobjekt blir ikke fylt av JSON.

 jsonTree: = '"value": 3, "left": "verdi": 1, "venstre": null, "høyre": "verdi": 2, "venstre": null, "høyre": null , "høyre": "verdi": 4, "venstre": null, "høyre": null 'var tree Tree err = json.Unmarshal ([] byte (dd) nil fmt.Printf ("- Kan ikke deserislisere tre, feil:% v \ n", err) else tree.Dump ("") Utgang: verdi: 0 igjen:  Ikke sant:  

Problemet er at trefeltene er private. JSON serialisering fungerer bare på offentlige felt. Så vi kan lage struct felt offentlig. Json-pakken er smart nok til å gjennomsiktig konvertere små bokstaver "verdi", "venstre" og "høyre" til de tilsvarende store feltnavnene.

Skriv inn Tree Struct Value int 'json:' value '' Venstre * Tree 'json: "left"' Høyre * Tree 'json: "right"' Utgang: verdi: 3 igjen: verdi: 1 igjen:  høyre: verdi: 2 igjen:  Ikke sant:  høyre: verdi: 4 igjen:  Ikke sant:  

Json-pakken vil tydeligvis ignorere unmapped-felt i JSON, så vel som private felt i din struct. Men noen ganger kan det være lurt å kartlegge bestemte nøkler i JSON til et felt med et annet navn i din struct. Du kan bruke struct tagger for det. For eksempel, anta at vi legger til et annet felt kalt "label" til JSON, men vi må kartlegge det til et felt kalt "Tag" i vår struct. 

Skriv inn Tree struktur Value int Tag streng 'json:' label '' Venstre * Tree Right * Tree func (t * Tree) Dump (inntrekkstreng) fmt.Println (indent + "verdi:", t.Value) hvis t.Tag! = "" fmt.Println (indent + "tag:", t.Tag) fmt.Print (indent + "left:") hvis t.Left == nil fmt.Println (null) ellers fmt.Println () t.Left.Dump (indent + "") fmt.Print (indent + "right:") hvis t.Right == nil fmt.Println (nil) else fmt.Println () t.Right.Dump (indent + "") 

Her er den nye JSON med rotnoden av treet merket som "root", serialisert riktig inn i Tag-feltet og trykt i utgangen:

 dd: = '"label": "root", "value": 3, "left": "verdi": 1, "venstre": null, "høyre": "verdi": 2, "venstre" : null, "høyre": null, "høyre": "verdi": 4, "venstre": null, "høyre": null 'var tree Tree err = json.Unmarshal ([] byte ), og tre) hvis err! = nil fmt.Printf ("- Kan ikke deserislisere tre, feil:% v \ n", err) annet tree.Dump ("") Output: value: 3 tag: rot venstre: verdi: 1 igjen:  høyre: verdi: 2 igjen:  Ikke sant:  høyre: verdi: 4 igjen:  Ikke sant: 

Skrive en tilpasset Marshaller

Du vil ofte serialisere objekter som ikke overholder de strenge kravene til Marshal () -funksjonen. For eksempel vil du kanskje serialisere et kart med int nøkler. I disse tilfellene kan du skrive en tilpasset marshaller / unmarshaller ved å implementere Marshaler og Unmarshaler grensesnitt.

Et notat om stavemåte: I Go, er konvensjonen å navngi et grensesnitt med en enkelt metode ved å legge til "er" suffikset til metodenavnet. Så, selv om den mer vanlige stavemåten er "Marshaller" (med dobbelt L), er grensesnittnavnet bare "Marshaler" (enkelt L).

Her er Marshaler og Unmarshaler grensesnitt:

skriv Marshaler-grensesnitt MarshalJSON () ([] byte, feil) skriv Unmarshaler-grensesnitt UnmarshalJSON ([] byte) feil 

Du må opprette en type når du gjør tilpasset serialisering, selv om du vil serialisere en innebygd type eller sammensetning av innebygde typer som kart [int] strengen. Her definerer jeg en type som kalles IntStringMap og implementere Marshaler og Unmarshaler grensesnitt for denne typen.

De MarshalJSON () Metoden skaper a kart [streng] streng, konverterer hver av sine egne int nøkler til en streng, og serialiserer kartet med strengtaster ved hjelp av standarden json.Marshal () funksjon.

skriv inn IntStringMap-kart [int] strengfunksjon (m * IntStringMap) MarshalJSON () ([] byte, feil) ss: = kart [streng] streng  for k, v: = rekkevidde * m i: = strconv.Itoa (k) ss [i] = v returnere json.Marshal (ss) 

Metoden UnmarshalJSON () gjør det motsatte. Det deserialiserer data byte array til a kart [streng] streng og konverterer deretter hver strengnøkkel til en int og fyller seg selv.

func (m * IntStringMap) UnmarshalJSON (data [] byte) feil ss: = map [streng] streng  err: = json.Unmarshal (data, & ss) hvis feil! = null return err for k, v: = rekkevidde ss i, err: = strconv.Atoi (k) hvis err! = null return err (* m) [i] = v retur null 

Slik bruker du det i et program:

 m: = IntStringMap 4: "fire", 5: "fem" data, feil: = m.MarshalJSON () hvis feil! = null fmt.Println (err) fmt.Println ("IntStringMap til JSON:" , streng (data)) m = IntStringMap  jsonString: = [] byte ("\" 1 \ ": \" one \ ", \" 2 \ ": \" two \ "") m.UnmarshalJSON jmString) fmt.Printf ("IntStringMap fra JSON:% v \ n", m) fmt.Println ("m [1]:", m [1], "m [2]:", m [2]) Utgang : IntStringMap til JSON: "4": "fire", "5": "fem" IntStringMap fra JSON: kart [2: to 1: en] m [1]: en m [2]: to

Serialiserende Enums

Go enums kan være ganske bekymrende å serialisere. Ideen om å skrive en artikkel om Go json serialisering kom ut av et spørsmål en kollega spurte meg om hvordan man serialiserer enums. Her er en Go enum. Konstanterne Zero og One er lik innsatsene 0 og 1.

skriv EnumType int const (Zero EnumType = iota One) 

Mens du kanskje tror det er en int, og i mange henseender er det, du kan ikke serialisere det direkte. Du må skrive en egendefinert marshaler / unmarshaler. Det er ikke et problem etter den siste delen. Følgende MarshalJSON () og UnmarshalJSON () vil serialisere / deserialisere konstanterne ZERO og ONE til / fra de tilsvarende strengene "Zero" og "One".

func (e * EnumType) UnmarshalJSON (data [] byte) feil var s streng err: = json.Unmarshal (data, & s) hvis feil! = null return err verdi, ok: = kart [streng] EnumType " Null ": Null," En ": En [s] hvis! Ok returfeil.Ny (" Ugyldig EnumType-verdi ") * e = verdi retur null func (e * EnumType) MarshalJSON () , feil) verdi, ok: = kart [EnumType] streng null: "null", ett: "ett" [* e] hvis! ok return null, feil.Ny ("ugyldig EnumType-verdi") returnere json.Marshal (verdi) 

La oss prøve å legge inn dette EnumType i en struct og serialisere det. Hovedfunksjonen skaper en EnumContainer og initialiserer det med navnet "Uno" og en verdi av vår enum konstant EN, som er lik int 1.

skriv EnumContainer struct Navn streng Verdi EnumType func main () x: = En ec: = EnumContainer "Uno", x, s, err: = json.Marshal (ec) hvis feil! = null fmt.Printf ("fail") var ec2 EnumContainer err = json.Unmarshal (s, og ec2) fmt.Println (ec2.Name, ":", ec2.Value) Utgang: Uno: 0 

Den forventede produksjonen er "Uno: 1", men i stedet er den "Uno: 0". Hva skjedde? Det er ingen feil i marshal / unmarshal-koden. Det viser seg at du ikke kan legge inn enums med verdi hvis du vil serialisere dem. Du må legge inn en peker til enummen. Her er en endret versjon der det virker som forventet:

skriv EnumContainer struct Navn streng Verdi * EnumType func main () x: = Én ec: = EnumContainer "Uno", & x, s, err: = json.Marshal (ec) hvis feil! = null fmt. Printf ("fail!") Var ec2 EnumContainer err = json.Unmarshal (s, & ec2) fmt.Println (ec2.Name, ":", * ec2.Value) Utgang: Uno: 1

Konklusjon

Go gir mange alternativer for serialisering og deserialisering av JSON. Det er viktig å forstå inn-og utgangene til kodingen / json-pakken for å utnytte strømmen.

Denne opplæringen legger all kraft i hendene dine, inkludert hvordan du serialiserer de unnvikende Go enums.

Gå serialisere noen objekter!