Introdução ao trabalho com JSON em Go
JSON (JavaScript Object Notation) é um formato leve de troca de dados, amplamente utilizado em aplicações web, APIs e microsserviços. A linguagem Go (Golang) fornece uma poderosa biblioteca embutida encoding/json, que permite realizar facilmente parsing (desserialização) e serialização de dados. Neste artigo, vamos detalhar como trabalhar com JSON em Go, abordando as principais funções, estruturas e melhores práticas.
Fundamentos de serialização e desserialização
Serialização é o processo de converter uma estrutura de dados do Go (por exemplo, uma struct ou slice) em uma string JSON. Desserialização é a conversão inversa, de JSON para uma estrutura do Go.
Serialização (Marshal)
Para serialização, utiliza-se a função json.Marshal(). Ela recebe um valor Go e retorna um slice de bytes ([]byte) com a representação JSON.
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
func main() {
user := User{Name: "Alexey", Age: 30, Email: "alex@example.com"}
jsonData, err := json.Marshal(user)
if err != nil {
fmt.Println("Erro de serialização:", err)
return
}
fmt.Println(string(jsonData))
// Saída: {"name":"Alexey","age":30,"email":"alex@example.com"}
}
Observe as tags json:"nome_do_campo". Elas permitem controlar os nomes das chaves no JSON, além de adicionar opções como omitempty (pular o campo se estiver vazio).
Desserialização (Unmarshal)
Para fazer o parsing de JSON para uma estrutura Go, utiliza-se a função json.Unmarshal(). Ela recebe um slice de bytes e um ponteiro para a estrutura onde os dados devem ser armazenados.
func main() {
jsonString := `{"name":"Maria","age":25,"email":"maria@example.com"}`
var user User
err := json.Unmarshal([]byte(jsonString), &user)
if err != nil {
fmt.Println("Erro de parsing:", err)
return
}
fmt.Printf("Nome: %s, Idade: %d, Email: %s", user.Name, user.Age, user.Email)
}
Importante: a estrutura deve ser predefinida, e seus campos devem corresponder às chaves JSON (considerando as tags).
Trabalhando com objetos JSON arbitrários
Às vezes, a estrutura do JSON é desconhecida antecipadamente ou muito complexa. Nesses casos, pode-se usar map[string]interface{} ou interface{}.
Usando map[string]interface{}
func main() {
jsonString := `{"name":"Ivan","age":35,"address":{"city":"Moscou","street":"Lenina"}}`
var result map[string]interface{}
err := json.Unmarshal([]byte(jsonString), &result)
if err != nil {
fmt.Println("Erro:", err)
return
}
fmt.Println(result["name"]) // Ivan
fmt.Println(result["age"]) // 35 (tipo float64)
// Para objetos aninhados, é necessário fazer type assertion
address := result["address"].(map[string]interface{})
fmt.Println(address["city"]) // Moscou
}
A desvantagem dessa abordagem é a necessidade de conversão manual de tipos (.(map[string]interface{})), o que pode causar pânico se os tipos não coincidirem.
Usando json.RawMessage
Se for necessário adiar o parsing de parte do JSON, use json.RawMessage. Isso permite primeiro analisar a estrutura principal e depois processar os dados aninhados separadamente.
type Response struct {
Status string `json:"status"`
Data json.RawMessage `json:"data"`
}
func main() {
jsonString := `{"status":"ok","data":{"user":"Pedro","score":100}}`
var resp Response
json.Unmarshal([]byte(jsonString), &resp)
// Agora é possível analisar Data separadamente
var userData map[string]interface{}
json.Unm
```