1. Visão geral da biblioteca padrão encoding/json
A linguagem Go fornece uma poderosa biblioteca encoding/json
para lidar com o formato de dados JSON. Com esta biblioteca, é possível converter facilmente tipos de dados do Go para o formato JSON (serialização) ou converter dados JSON para tipos de dados do Go (desserialização). Esta biblioteca oferece muitas funcionalidades, como codificação, decodificação, E/S de streaming e suporte para lógica de análise JSON personalizada.
Os tipos de dados e funções mais importantes nesta biblioteca incluem:
-
Marshal
eMarshalIndent
: usados para serializar tipos de dados do Go em strings JSON. -
Unmarshal
: usado para desserializar strings JSON em tipos de dados do Go. -
Encoder
eDecoder
: usados para E/S de streaming de dados JSON. -
Valid
: usado para verificar se uma determinada string é um formato JSON válido.
Especificamente, iremos aprender o uso dessas funções e tipos nos próximos capítulos.
2. Serializando estruturas de dados do Go para JSON
2.1 Usando json.Marshal
json.Marshal
é uma função que serializa tipos de dados do Go em strings JSON. Ela recebe tipos de dados da linguagem Go como entrada, converte-os para o formato JSON e retorna uma série de bytes juntamente com possíveis erros.
Aqui está um exemplo simples que demonstra como converter uma estrutura do Go para uma string JSON:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Pessoa struct {
Nome string `json:"nome"`
Idade int `json:"idade"`
}
func main() {
pessoa := Pessoa{"Alice", 30}
jsonData, err := json.Marshal(pessoa)
if err != nil {
log.Fatalf("Falha na serialização JSON: %s", err)
}
fmt.Println(string(jsonData)) // Saída: {"nome":"Alice","idade":30}
}
Além de estruturas, a função json.Marshal
também pode serializar outros tipos de dados, como mapas e slices. Abaixo estão exemplos usando map[string]interface{}
e slice
:
// Converter mapa para JSON
meuMapa := map[string]interface{}{
"nome": "Bob",
"idade": 25,
}
jsonData, err := json.Marshal(meuMapa)
// ... tratamento de erro e saída omitidos ...
// Converter slice para JSON
meuSlice := []string{"Maçã", "Banana", "Cereja"}
jsonData, err := json.Marshal(meuSlice)
// ... tratamento de erro e saída omitidos ...
2.2 Tags de Struct
Em Go, as tags de struct são usadas para fornecer metadados para os campos da struct, controlando o comportamento da serialização JSON. Os casos de uso mais comuns incluem renomear campos, ignorar campos e serialização condicional.
Por exemplo, é possível usar a tag json:"<nome>"
para especificar o nome do campo JSON:
type Animal struct {
NomeEspecie string `json:"especie"`
Descricao string `json:"desc,omitempty"`
Tag string `json:"-"` // Adicionando a tag "-" indica que este campo não será serializado
}
No exemplo acima, a tag json:"-"
na frente do campo Tag
indica que json.Marshal
deve ignorar este campo. A opção omitempty
para o campo Description
indica que se o campo estiver vazio (valor zero, como uma string vazia), ele não será incluído no JSON serializado.
Aqui está um exemplo completo usando tags de struct:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Animal struct {
NomeEspecie string `json:"especie"`
Descricao string `json:"desc,omitempty"`
Tag string `json:"-"`
}
func main() {
animal := Animal{
NomeEspecie: "Elefante Africano",
Descricao: "Um mamífero grande com tromba e presas.",
Tag: "ameaçado", // Este campo não será serializado para JSON
}
jsonData, err := json.Marshal(animal)
if err != nil {
log.Fatalf("Falha na serialização JSON: %s", err)
}
fmt.Println(string(jsonData)) // Saída: {"especie":"Elefante Africano","desc":"Um mamífero grande com tromba e presas."}
}
Desta forma, é possível garantir uma estrutura de dados clara, controlando a representação JSON, lidando de maneira flexível com várias necessidades de serialização.
3. Desserializar JSON para Estrutura de Dados do Go
3.1 Utilizando json.Unmarshal
A função json.Unmarshal
nos permite analisar strings JSON em estruturas de dados Go, como structs, maps, etc. Para utilizar o json.Unmarshal
, primeiro precisamos definir uma estrutura de dados Go que corresponda aos dados JSON.
Supondo que temos os seguintes dados JSON:
{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}
Para analisar esses dados em uma struct Go, precisamos definir uma struct correspondente:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Emails []string `json:"emails"`
}
Agora podemos usar o json.Unmarshal
para desserialização:
import (
"encoding/json"
"fmt"
)
func main() {
jsonData := `{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}`
var user User
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Erro ao desserializar JSON:", err)
return
}
fmt.Printf("Usuário: %+v\n", user)
}
No exemplo acima, usamos tags como json: "name"
para informar a função json.Unmarshal
sobre a correspondência dos campos JSON com os campos da struct.
3.2 Análise Dinâmica
Às vezes, a estrutura JSON que precisamos analisar não é conhecida antecipadamente, ou a estrutura dos dados JSON pode mudar dinamicamente. Nesses casos, podemos usar interface{}
ou json.RawMessage
para análise.
Usar interface{}
nos permite analisar sem conhecer a estrutura JSON:
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Engenheira"
}
}`
var result map[string]interface{}
json.Unmarshal([]byte(jsonData), &result)
fmt.Println(result)
// Assertivas de tipo, garantir correspondência de tipo antes de usar
name := result["name"].(string)
fmt.Println("Nome:", name)
details := result["details"].(map[string]interface{})
age := details["age"].(float64) // Nota: números em interface{} são tratados como float64
fmt.Println("Idade:", age)
}
Usar json.RawMessage
nos permite manter o JSON original enquanto analisamos seletivamente suas partes:
type UserDynamic struct {
Name string `json:"name"`
Details json.RawMessage `json:"details"`
}
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Engenheira"
}
}`
var user UserDynamic
json.Unmarshal([]byte(jsonData), &user)
var details map[string]interface{}
json.Unmarshal(user.Details, &details)
fmt.Println("Nome:", user.Name)
fmt.Println("Idade:", details["age"])
fmt.Println("Trabalho:", details["job"])
}
Essa abordagem é útil para lidar com estruturas JSON onde certos campos podem conter diferentes tipos de dados, e permite uma manipulação flexível de dados.
4 Lidando com Estruturas Aninhadas e Arrays
4.1 Objetos JSON Aninhados
Os dados JSON comuns frequentemente não são planos, mas contêm estruturas aninhadas. Em Go, podemos lidar com essa situação definindo structs aninhadas.
Suponha que temos o seguinte JSON aninhado:
{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}
Podemos definir a struct Go da seguinte forma:
type ContactInfo struct {
Email string `json:"email"`
Address string `json:"address"`
}
type UserWithContact struct {
Name string `json:"name"`
Contact ContactInfo `json:"contact"`
}
A operação de desserialização é semelhante às estruturas não aninhadas:
func main() {
jsonData := `{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}`
var user UserWithContact
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Erro ao desserializar JSON:", err)
}
fmt.Printf("%+v\n", user)
}
4.2 Arrays JSON
Em JSON, arrays são uma estrutura de dados comum. Em Go, eles correspondem a slices.
Considere o seguinte array JSON:
[
{"name": "Dave", "age": 34},
{"name": "Eve", "age": 28}
]
Em Go, definimos a struct correspondente e a slice da seguinte forma:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonData := `[
{"name": "Dave", "age": 34},
{"name": "Eve", "age": 28}
]`
var people []Person
json.Unmarshal([]byte(jsonData), &people)
for _, person := range people {
fmt.Printf("%+v\n", person)
}
}
Desta forma, podemos desserializar cada elemento no array JSON em uma slice de structs Go para processamento e acesso posterior.
5 Tratamento de Erros
Ao lidar com dados JSON, seja serialização (conversão de dados estruturados para formato JSON) ou desserialização (conversão de JSON de volta para dados estruturados), podem ocorrer erros. A seguir, discutiremos erros comuns e como lidar com eles.
5.1 Tratamento de Erros de Serialização
Erros de serialização ocorrem tipicamente durante o processo de conversão de uma struct ou outros tipos de dados em uma string JSON. Por exemplo, se uma tentativa for feita de serializar uma struct contendo campos ilegais (como um tipo de canal ou função que não pode ser representado em JSON), json.Marshal
retornará um erro.
import (
"encoding/json"
"fmt"
"log"
)
type User struct {
Name string
Age int
// Vamos supor que haja um campo aqui que não pode ser serializado
// Data chan struct{} // Canais não podem ser representados em JSON
}
func main() {
u := User{
Name: "Alice",
Age: 30,
// Data: make(chan struct{}),
}
bytes, err := json.Marshal(u)
if err != nil {
log.Fatalf("Falha na serialização JSON: %v", err)
}
fmt.Println(string(bytes))
}
No exemplo acima, intencionalmente comentamos o campo Data
. Se descomentado, a serialização falhará e o programa registrará o erro e encerrará a execução. Lidar com tais erros geralmente envolve a verificação de erros e implementação de estratégias de tratamento de erros correspondentes (como registrar erros, retornar dados padrão, etc.).
5.2 Tratamento de Erros de Desserialização
Erros de desserialização podem ocorrer durante o processo de conversão de uma string JSON de volta para uma struct Go ou outro tipo de dados. Por exemplo, se o formato da string JSON estiver incorreto ou incompatível com o tipo alvo, json.Unmarshal
retornará um erro.
import (
"encoding/json"
"fmt"
"log"
)
func main() {
var data = []byte(`{"name":"Alice","age":"unknown"}`) // "age" deveria ser um número inteiro, mas aqui foi fornecida uma string
var u User
err := json.Unmarshal(data, &u)
if err != nil {
log.Fatalf("Falha na desserialização JSON: %v", err)
}
fmt.Printf("%+v\n", u)
}
Neste exemplo de código, fornecemos intencionalmente o tipo de dados errado para o campo age
(uma string em vez do número inteiro esperado), causando o lançamento de um erro pelo json.Unmarshal
. Portanto, precisamos lidar com esta situação apropriadamente. A prática comum é registrar a mensagem de erro e, dependendo do cenário, possivelmente retornar um objeto vazio, valor padrão ou mensagem de erro.
6 Recursos Avançados e Otimização de Desempenho
6.1 Personalização de Marshal e Unmarshal
Por padrão, o pacote encoding/json
em Go serializa e desserializa JSON por meio de reflexão. No entanto, podemos personalizar esses processos implementando as interfaces json.Marshaler
e json.Unmarshaler
.
import (
"encoding/json"
"fmt"
)
type Color struct {
Red uint8
Green uint8
Blue uint8
}
func (c Color) MarshalJSON() ([]byte, error) {
hex := fmt.Sprintf("\"#%02x%02x%02x\"", c.Red, c.Green, c.Blue)
return []byte(hex), nil
}
func (c *Color) UnmarshalJSON(data []byte) error {
_, err := fmt.Sscanf(string(data), "\"#%02x%02x%02x\"", &c.Red, &c.Green, &c.Blue)
return err
}
func main() {
c := Color{Red: 255, Green: 99, Blue: 71}
jsonColor, _ := json.Marshal(c)
fmt.Println(string(jsonColor))
var newColor Color
json.Unmarshal(jsonColor, &newColor)
fmt.Println(newColor)
}
Aqui, definimos um tipo Color
e implementamos os métodos MarshalJSON
e UnmarshalJSON
para converter cores RGB em strings hexadecimais e depois de volta para cores RGB.
6.2 Codificadores e Decodificadores
Ao lidar com grandes dados JSON, o uso direto de json.Marshal
e json.Unmarshal
pode levar a um consumo excessivo de memória ou a operações de entrada/saída ineficientes. Portanto, o pacote encoding/json
em Go fornece os tipos Encoder
e Decoder
, que podem processar dados JSON de forma contínua.
6.2.1 Utilizando json.Encoder
json.Encoder
pode escrever diretamente dados JSON para qualquer objeto que implemente a interface io.Writer, o que significa que você pode codificar dados JSON diretamente para um arquivo, conexão de rede, etc.
import (
"encoding/json"
"os"
)
func main() {
users := []User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}
file, _ := os.Create("users.json")
defer file.Close()
encoder := json.NewEncoder(file)
if err := encoder.Encode(users); err != nil {
log.Fatalf("Erro de codificação: %v", err)
}
}
6.2.2 Utilizando json.Decoder
json.Decoder
pode ler diretamente dados JSON de qualquer objeto que implemente a interface io.Reader, buscando e analisando objetos e arrays JSON.
import (
"encoding/json"
"os"
)
func main() {
file, _ := os.Open("users.json")
defer file.Close()
var users []User
decoder := json.NewDecoder(file)
if err := decoder.Decode(&users); err != nil {
log.Fatalf("Erro de decodificação: %v", err)
}
for _, u := range users {
fmt.Printf("%+v\n", u)
}
}
Ao processar dados com codificadores e decodificadores, você pode realizar o processamento JSON enquanto lê, reduzindo o uso de memória e melhorando a eficiência de processamento, o que é especialmente útil para lidar com transferências em rede ou arquivos grandes.