Da série go para devs php:
Existe uma diferença notável em como as linguagens tratam json e
se você está estudando GO e não achou os galãs json_encode
e json_decode
esse artigo é pra você!
Index
Intro
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:
$ 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
Agora para trabalhar com o dado:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| package main
import (
"encoding/json"
"fmt"
)
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
}
func main() {
// ==== 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:
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| package main
import (
"encoding/json"
"fmt"
)
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
}
func main() {
// ==== Person ====
var person Person
json.Unmarshal([]byte(jsonPerson), &person)
fmt.Printf("[Person] Name: %s - Age: %d\n", person.Name, person.Age)
// ==== Tech ====
var tech Tech
json.Unmarshal([]byte(jsonTech), &tech)
fmt.Printf("[Tech] Project: %s - Stack: %s\n", tech.Project, tech.Stack)
}
|
Resultado:
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.
E o decoding funciona da mesma forma:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| package main
import (
"encoding/json"
"fmt"
)
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
}
func main() {
// ==== Person ====
var person Person
json.Unmarshal([]byte(jsonPerson), &person)
fmt.Printf("[Person] Name: %s - Age: %d\n", person.Name, person.Age)
// ==== Tech ====
var tech Tech
json.Unmarshal([]byte(jsonTech), &tech)
fmt.Printf("[Tech] Project: %s - Stack: %s\n", tech.Project, tech.Stack)
// ==== Country ====
var country Country
json.Unmarshal([]byte(jsonCountry), &country)
fmt.Printf("[Country] Location: %s - Goods: %s\n", country.Location, country.Goods)
}
|
Resultado:
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| package main
import (
"encoding/json"
"fmt"
)
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
}
func main() {
decode()
encode()
}
func decode() {
// ==== Person ====
var person Person
json.Unmarshal([]byte(jsonPerson), &person)
fmt.Printf("[Person] Name: %s - Age: %d\n", person.Name, person.Age)
// ==== Tech ====
var tech Tech
json.Unmarshal([]byte(jsonTech), &tech)
fmt.Printf("[Tech] Project: %s - Stack: %s\n", tech.Project, tech.Stack)
// ==== Country ====
var country Country
json.Unmarshal([]byte(jsonCountry), &country)
fmt.Printf("[Country] Location: %s - Goods: %s\n", country.Location, country.Goods)
}
func encode() {
}
|
Similar ao decoding iremos nos basear nas estruturas que já possuímos. Agora podemos utilizar a função json.Marshal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| func encode() {
// ==== Person ====
rodrigo := &Person{
Name: "Rodrigo",
Age: 28,
}
dataPerson, _ := json.Marshal(rodrigo)
fmt.Println(string(dataPerson))
// ==== Tech ====
myStack := &Tech{
Project: "Foo/Bar",
Stack: []string{"Elk", "Elixir", "Go"},
}
dataStack, _ := json.Marshal(myStack)
fmt.Println(string(dataStack))
// ==== Country ====
brazil := &Country{
Location: "Brazil",
Goods: []FoodPerDay{
{
Day: "Wednesday",
Food: "Feijoada e caipirinha",
},
{
Day: "Monday",
Food: "Virado à paulista",
},
{
Day: "Friday",
Food: "Arroz com molho de camarão e Merluza",
},
},
}
dataCountry, _ := json.Marshal(brazil)
fmt.Println(string(dataCountry))
}
|
Resultado:
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:
1
2
| dataStack, _ := json.Marshal(myStack)
fmt.Println(string(dataStack))
|
- É necessário passar a referência da struct que será preenchida, por isso a utilização de
&
no inicio da struct que está sendo construída:
1
2
3
4
| myStack := &Tech{
Project: "Foo/Bar",
Stack: []string{"Elk", "Elixir", "Go"},
}
|
- Para “pretty prints” podemos utilizar a função
json.MarshalIndent
Veja o exemplo no json de country:
1
2
| dataCountry, _ := json.MarshalIndent(brazil, "", " ")
fmt.Println(string(dataCountry))
|
resultado:
{
"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"
}
]
}
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
}
myStack := &Tech{
Project: "Foo/Bar",
Stack: []string{"Elk", "Elixir", "Go"},
}
dataStack, _ := json.Marshal(myStack)
fmt.Println(string(dataStack))
|
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:
{"projeto":"Foo/Bar","Stack":["Elk","Elixir","Go"]}
E o contrário:
1
2
3
4
5
6
7
8
9
10
| var jsonTech = `{"projeto": "foo/bar", "stack": ["go", "elk", "elixir"]}`
type Tech struct {
Project string `json:"projeto"`
Stack []string
}
var tech Tech
json.Unmarshal([]byte(jsonTech), &tech)
fmt.Printf("[Tech] Project: %s - Stack: %s\n", tech.Project, tech.Stack)
|
[Tech] Project: foo/bar - Stack: [go elk elixir]
Fora o mapeamento básico temos algumas opções interessantes como:
ex:
1
2
3
4
| type Person struct {
Name string `json:"projeto,omitempty"`
Password string `json:"-"`
}
|
Para mais informações:
Considerações
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
Ref