Roadmap Golang: Marshalling e Unmarshalling JSON
Vou pular a parte de explicação do JSON. haha! Vamos para o que interessa.
Marshalling é o processo de transformar um objeto ou estrutura de dados em um formato que possa ser facilmente armazenado ou transmitido. No caso do JSON, marshalling é transformar uma estrutura em Go (struct) para uma string JSON.
Unmarshalling é o processo inverso: transformar uma string JSON em um objeto ou estrutura de dados. Em Go, isso significa pegar uma string JSON e preencher uma struct com esses dados.
Golang e JSON
Golang tem um suporte nativo bem robusto para trabalhar com JSON através do pacote encoding/json. Vamos ver como funciona na prática.
Estrutura Básica
Primeiro, vamos definir uma estrutura (struct) em Go. Imagine que você quer representar uma pessoa:
package main
import (
    "encoding/json"
    "fmt"
)
type Pessoa struct {
    Nome  string `json:"nome"`
    Idade int    `json:"idade"`
    Email string `json:"email"`
}
A struct Pessoa tem três campos: Nome, Idade e Email. Note que usamos tags JSON para mapear os campos da struct para os campos do JSON.
Marshalling
Vamos transformar essa struct em uma string JSON:
func main() {
    pessoa := Pessoa{
        Nome:  "João",
        Idade: 30,
        Email: "joao@example.com",
    }
    jsonData, err := json.Marshal(pessoa)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(jsonData))
}
Saída:
{"nome":"João","idade":30,"email":"joao@example.com"}
Aqui usamos json.Marshal para converter a struct Pessoa em uma string JSON. Se houver algum erro durante o processo, ele será capturado e impresso.
Unmarshalling
Agora, vamos pegar uma string JSON e convertê-la de volta para uma struct:
func main() {
    jsonData := []byte(`{"nome":"João","idade":30,"email":"joao@example.com"}`)
    var pessoa Pessoa
    err := json.Unmarshal(jsonData, &pessoa)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("%+v
", pessoa)
}
Saída:
{Nome:João Idade:30 Email:joao@example.com}
Aqui usamos json.Unmarshal para converter a string JSON de volta para a struct Pessoa. Note que precisamos passar um ponteiro para a struct.
Exemplos Mais Complexos
Vamos complicar um pouco as coisas. Imagine que você tem um JSON com uma lista de pessoas:
[
    {
        "nome": "João",
        "idade": 30,
        "email": "joao@example.com"
    },
    {
        "nome": "Maria",
        "idade": 25,
        "email": "maria@example.com"
    }
]
Podemos representar isso em Go como um slice de Pessoa:
func main() {
    jsonData := []byte(`[
        {"nome":"João","idade":30,"email":"joao@example.com"},
        {"nome":"Maria","idade":25,"email":"maria@example.com"}
    ]`)
    var pessoas []Pessoa
    err := json.Unmarshal(jsonData, &pessoas)
    if err != nil {
        fmt.Println(err)
        return
    }
    for _, pessoa := range pessoas {
        fmt.Printf("%+v
", pessoa)
    }
}
Saída:
{Nome:João Idade:30 Email:joao@example.com}
{Nome:Maria Idade:25 Email:maria@example.com}
Aqui, deserializamos um slice de Pessoa a partir de um JSON.
Trabalhando com Campos Opcionais
Às vezes, os campos do JSON podem ser opcionais. Por exemplo:
{
    "nome": "João",
    "idade": 30
}
Aqui, o campo email está faltando. Podemos lidar com isso usando ponteiros ou o tipo omitempty nas tags JSON:
type Pessoa struct {
    Nome  string  `json:"nome"`
    Idade int     `json:"idade"`
    Email *string `json:"email,omitempty"`
}
Marshalling e Unmarshalling com Mapas
Também podemos usar mapas em vez de structs. Isso é útil quando não conhecemos a estrutura do JSON de antemão:
func main() {
    jsonData := []byte(`{"nome":"João","idade":30,"email":"joao@example.com"}`)
    var pessoa map[string]interface{}
    err := json.Unmarshal(jsonData, &pessoa)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("%+v
", pessoa)
}
Saída:
map[email:joao@example.com idade:30 nome:João]
Customizando a Serialização e Desserialização
Às vezes, você precisa de mais controle sobre como os dados são serializados e desserializados. Para isso, você pode implementar as interfaces json.Marshaler e json.Unmarshaler.
Por exemplo, suponha que você quer que o campo Idade seja sempre serializado como uma string:
type Pessoa struct {
    Nome  string `json:"nome"`
    Idade int    `json:"idade,string"`
    Email string `json:"email"`
}
Ou, você pode implementar métodos customizados:
func (p Pessoa) MarshalJSON() ([]byte, error) {
    type Alias Pessoa
    return json.Marshal(&struct {
        Idade string `json:"idade"`
        *Alias
    }{
        Idade: fmt.Sprintf("%d anos", p.Idade),
        Alias: (*Alias)(&p),
    })
}
func main() {
    pessoa := Pessoa{
        Nome:  "João",
        Idade: 30,
        Email: "joao@example.com",
    }
    jsonData, err := json.Marshal(pessoa)
    if (err != nil) {
        fmt.Println(err)
        return
    }
    fmt.Println(string(jsonData))
}
Saída:
{"nome":"João","idade":"30 anos","email":"joao@example.com"}
Lidando com Erros
Sempre que trabalhamos com JSON, é importante lidar com possíveis erros de forma adequada. Veja como podemos fazer isso:
func main() {
    jsonData := []byte(`{"nome":"João","idade":"trinta","email":"joao@example.com"}`)
    var pessoa Pessoa
    err := json.Unmarshal(jsonData, &pessoa)
    if err != nil {
        fmt.Println("Erro ao desserializar JSON:", err)
        return
    }
    fmt.Printf("%+v
", pessoa)
}
Saída:
Erro ao desserializar JSON: json: cannot unmarshal string into Go struct field Pessoa.idade of type int
Entender Marshalling e Unmarshalling é fundamental para manipular dados.
Lembre-se de sempre lidar com erros e validar seus dados. E, claro, continue praticando e experimentando com diferentes tipos de dados e estruturas. Borá codar!