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!


<
Previous Post
Roadmap Golang: Go Modules
>
Next Post
Nova vulnerabilidade OpenSSH pode levar ao RCE como root em sistemas Linux