O pacote encoding/json da biblioteca padrão do go possui todas as funcionalidades necessárias para encodar/decodar json. Quando lidamos com json dentro do go por se tratar de uma linguagem com tipagem forte precisamos “mapear” os campos de um json para uma estrutura que faça sentido
Iniciando o projeto
Vamos criar um projeto simples:
1 2
$ mkdir jsonbyexample; cd $_ $ go mod init jsonbyexample
Com isso podemos criar um arquivo main.go com algumas coisas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
package main
var ( jsonPerson = `{"name": "Rodrigo", "age": 28}` jsonTech = `{"project": "foo/bar", "stack": ["go", "elk", "elixir"]}` jsonCountry = `{ "location": "Brazil", "goods" :[ {"day": "Wednesday", "food": "Feijoada e caipirinha"}, {"day": "Monday", "food": "Virado à paulista"}, {"day": "Friday", "food": "Arroz com molho de camarão e Merluza"} ] }` )
Criamos alguns formatos jsons para explorar:
Chave e valor
Chave e valor + array
Chave e valor + array de objetos
Decoding json (Unmarshal)
Vamos começar convertendo os jsons para trabalha-los no go. Para isso precisamos criar estruturas compatíveis. Vamos começar com o jsonPerson:
1 2 3 4
type Person struct { Name string Age int }
Aqui mapeamos os campos Name como string e Age como int que é de fato o tipo contido no json
Importante: repare que seguindo a convenção da linguagem a primeira letra de cada campo está em maíusculo indicando que esse campo está sendo exportado e que pode ser acessado posteriormente
var ( jsonPerson = `{"name": "Rodrigo", "age": 28}` jsonTech = `{"project": "foo/bar", "stack": ["go", "elk", "elixir"]}` jsonCountry = `{ "location": "Brazil", "goods" :[ {"day": "Wednesday", "food": "Feijoada e caipirinha"}, {"day": "Monday", "food": "Virado à paulista"}, {"day": "Friday", "food": "Arroz com molho de camarão e Merluza"} ] }` )
type Person struct { Name string Age int }
funcmain() { // ==== Person ==== var person Person json.Unmarshal([]byte(jsonPerson), &person) fmt.Printf("[Person] Name: %s - Age: %d\n", person.Name, person.Age) }
Aqui fazemos a utilização da função json.Unmarshal que espera o dado a ser decodado no formato []byte e a refêrencia à variavel a ser preenchida &person
E para acessar os dados usamos dotNotation como no exemplo acima
Resultado:
1 2
girorme@machine ~/repositorios/go/jsonbyexample $ go run . [Person] Name: Rodrigo - Age: 28
Vamos agora mapear o json jsonTech que uma das chaves é um array de strings:
1 2 3 4
type Tech struct { Project string Stack []string }
Simplesmente indicamos que a chave Stack é um array de strings. O decoding continua simples:
var ( jsonPerson = `{"name": "Rodrigo", "age": 28}` jsonTech = `{"project": "foo/bar", "stack": ["go", "elk", "elixir"]}` jsonCountry = `{ "location": "Brazil", "goods" :[ {"day": "Wednesday", "food": "Feijoada e caipirinha"}, {"day": "Monday", "food": "Virado à paulista"}, {"day": "Friday", "food": "Arroz com molho de camarão e Merluza"} ] }` )
type Person struct { Name string Age int }
type Tech struct { Project string Stack []string }
funcmain() { // ==== Person ==== var person Person json.Unmarshal([]byte(jsonPerson), &person) fmt.Printf("[Person] Name: %s - Age: %d\n", person.Name, person.Age)
girorme@machine ~/repositorios/go/jsonbyexample $ go run . [Person] Name: Rodrigo - Age: 28 [Tech] Project: foo/bar - Stack: [go elk elixir]
Vamos agora mapear o json jsonCountry que umas das chaves é um array de objetos. Para alcançar esse mapping precisamos de uma struct auxiliar que possui as chaves respectivas:
1 2 3 4 5 6 7 8 9
type Country struct { Location string Goods []FoodPerDay }
type FoodPerDay struct { Day string Food string }
Sabemos que nosso json possui um array Goods de objetos FoodPerDay que contém as chaves Day e Food respectivamente.
var ( jsonPerson = `{"name": "Rodrigo", "age": 28}` jsonTech = `{"project": "foo/bar", "stack": ["go", "elk", "elixir"]}` jsonCountry = `{ "location": "Brazil", "goods" :[ {"day": "Wednesday", "food": "Feijoada e caipirinha"}, {"day": "Monday", "food": "Virado à paulista"}, {"day": "Friday", "food": "Arroz com molho de camarão e Merluza"} ] }` )
type Person struct { Name string Age int }
type Tech struct { Project string Stack []string }
type Country struct { Location string Goods []FoodPerDay }
type FoodPerDay struct { Day string Food string }
funcmain() { // ==== Person ==== var person Person json.Unmarshal([]byte(jsonPerson), &person) fmt.Printf("[Person] Name: %s - Age: %d\n", person.Name, person.Age)
// ==== Country ==== var country Country json.Unmarshal([]byte(jsonCountry), &country) fmt.Printf("[Country] Location: %s - Goods: %s\n", country.Location, country.Goods) }
Resultado:
1 2 3 4
girorme@DESKTOP-MN7HFPO ~/repositorios/go/jsonbyexample $ go run . [Person] Name: Rodrigo - Age: 28 [Tech] Project: foo/bar - Stack: [go elk elixir] [Country] Location: Brazil - Goods: [{Wednesday Feijoada e caipirinha} {Monday Virado à paulista} {Friday Arroz com molho de camarão e Merluza}]
Encoding Json (Marshal)
Agora vamos ao encoding de json. Vamos separar o arquivo em duas funções: decode() e encode() para melhorar visualização:
var ( jsonPerson = `{"name": "Rodrigo", "age": 28}` jsonTech = `{"project": "foo/bar", "stack": ["go", "elk", "elixir"]}` jsonCountry = `{ "location": "Brazil", "goods" :[ {"day": "Wednesday", "food": "Feijoada e caipirinha"}, {"day": "Monday", "food": "Virado à paulista"}, {"day": "Friday", "food": "Arroz com molho de camarão e Merluza"} ] }` )
type Person struct { Name string Age int }
type Tech struct { Project string Stack []string }
type Country struct { Location string Goods []FoodPerDay }
type FoodPerDay struct { Day string Food string }
funcmain() { decode() encode() }
funcdecode() { // ==== Person ==== var person Person json.Unmarshal([]byte(jsonPerson), &person) fmt.Printf("[Person] Name: %s - Age: %d\n", person.Name, person.Age)
// ==== Country ==== var country Country json.Unmarshal([]byte(jsonCountry), &country) fmt.Printf("[Country] Location: %s - Goods: %s\n", country.Location, country.Goods) }
funcencode() {
}
Similar ao decoding iremos nos basear nas estruturas que já possuímos. Agora podemos utilizar a função json.Marshal
girorme@machine ~/repositorios/go/jsonbyexample $ go run . ==== Decode ==== [Person] Name: Rodrigo - Age: 28 [Tech] Project: foo/bar - Stack: [go elk elixir] [Country] Location: Brazil - Goods: [{Wednesday Feijoada e caipirinha} {Monday Virado à paulista} {Friday Arroz com molho de camarão e Merluza}] ==== Encode ==== {"Name":"Rodrigo","Age":28} {"Project":"Foo/Bar","Stack":["Elk","Elixir","Go"]} {"Location":"Brazil","Goods":[{"Day":"Wednesday","Food":"Feijoada e caipirinha"},{"Day":"Monday","Food":"Virado à paulista"},{"Day":"Friday","Food":"Arroz com molho de camarão e Merluza"}]}
Observações
A função json.Marshal retorna []byte (array de byte) então precisamos converter o output para string:
{ "Location": "Brazil", "Goods": [ { "Day": "Wednesday", "Food": "Feijoada e caipirinha" }, { "Day": "Monday", "Food": "Virado à paulista" }, { "Day": "Friday", "Food": "Arroz com molho de camarão e Merluza" } ] }
Struct tags
Podemos também utilizar tags de structs para aperfeiçoar a exibição/parsing de json. Muitas bibliotecas fazem o uso extensivo da feature para consolidar comportamentos diversos.
Para json podemos usar a tag: json:field,options
Com as tags podemos: mapear campos de struct para campos específicos do json, exibir um campo com nome diferente e ainda definir alguns comportamentos como omissão de chave/valor, considere o exemplo abaixo:
1 2 3 4 5 6 7 8 9 10 11 12
type Tech struct { Project string`json:"projeto"` Stack []string }
a expressão acima faz tanto com que o output do campo Project seja exibido como projeto ou até msm o mapeamento inverso, onde o campo projeto irá ser exibido no campo food:
Existem casos onde não sabemos o json que está chegando e não queremos mapear isso via struct, para isso podemos resolver de algumas formas utilizando mapping para map[T]interface{} ou até msm com a estrutura json.RawMessage mas prefiro deixar para outro artigo já que esse já está um pouco “pesado” para quem está iniciando em GO xD