Skip to main content


Trabalhando com JSON​


O JSON (JavaScript Object Notation) é um formato de texto usado para representar e trocar dados entre programas. Ele é bastante comum em APIs, arquivos de configuração e aplicações que precisam enviar ou receber informações. Em Go, podemos trabalhar com JSON usando o package encoding/json, que faz parte da biblioteca padrão e permite converter dados do programa para JSON e também fazer o processo contrário.



O que é JSON?​


O JSON organiza informações usando pares de chave e valor, de forma parecida com os maps que já estudamos em Go ou dicionário em Python. Por exemplo, podemos representar as informações de um usuário assim:

{
"nome": "Ana",
"idade": 25,
"ativo": true
}

Nesse exemplo, nome, idade e ativo representam as chaves, enquanto "Ana", 25 e true representam seus respectivos valores. O JSON permite trabalhar com textos, números, booleanos, arrays, objetos e valores null. Diferentemente de uma struct em Go, o JSON não precisa declarar previamente os tipos dos campos, porque eles são representados diretamente no conteúdo.



Package encoding/json​


O package encoding/json permite converter dados entre estruturas do Go e o formato JSON. As duas funções principais são json.Marshal() e json.Unmarshal(). O Marshal() transforma um valor Go em JSON, enquanto o Unmarshal() faz o contrário, recebendo um JSON e preenchendo uma variável Go com os dados encontrados.


  • Marshal
    Struct Go para JSON.

  • Unmarshal
    JSON para Struct Go.


Essas operações são conhecidas como serialização e desserialização, porque permitem transformar os dados em um formato que pode ser armazenado ou transmitido e depois reconstruído em uma estrutura do programa.



Convertendo structs para JSON com Marshal​


A função json.Marshal() recebe um valor Go e retorna sua representação em JSON como []byte, além de um possível erro. Por exemplo:

package main

import (
"encoding/json"
"fmt"
)

type Usuario struct {
Nome string
Idade int
}

func main() {
usuario := Usuario{
Nome: "Ana",
Idade: 25,
}

// Converte a struct para JSON.
dados, err := json.Marshal(usuario)

if err != nil {
fmt.Println("Erro ao converter para JSON:", err)
return
}

// Converte os bytes para string.
fmt.Println(string(dados))
}

Resultado:

{"Nome":"Ana","Idade":25}

O json.Marshal() recebe a struct Usuario e converte seus campos exportados para JSON. Observe que o resultado é retornado como []byte, por isso fazemos a conversão para string antes de mostrar seu conteúdo.

fmt.Println(string(dados))

Os nomes das chaves seguem os nomes dos campos da struct, mas podemos personalizar esse comportamento usando tags JSON, que veremos mais adiante. Também podemos usar json.MarshalIndent() quando queremos gerar um JSON formatado, com espaços e quebras de linha.

// Gera um JSON formatado com indentação de 4 espaços.
dados, err := json.MarshalIndent(usuario, "", " ")

Nesse caso, o resultado será:

{
"Nome": "Ana",
"Idade": 25
}


Convertendo JSON para structs com Unmarshal​


A função json.Unmarshal() faz o processo contrário ao Marshal, recebendo um JSON e convertendo seus dados para uma variável Go. Para isso, precisamos informar o conteúdo JSON e o endereço da variável que receberá os valores.

package main

import (
"encoding/json"
"fmt"
)

type Usuario struct {
Nome string
Idade int
}

func main() {
// JSON recebido como texto.
dados := `{"Nome":"Carlos","Idade":30}`

var usuario Usuario

// Converte o JSON para a struct.
err := json.Unmarshal([]byte(dados), &usuario)

if err != nil {
fmt.Println("Erro ao converter JSON:", err)
return
}

fmt.Println(usuario.Nome)
fmt.Println(usuario.Idade)
}

Resultado:

Carlos
30

Nesse exemplo, usamos:

json.Unmarshal([]byte(dados), &usuario)

O []byte(dados) converte o texto JSON para uma sequência de bytes, enquanto &usuario passa o endereço da variável que receberá os valores. Isso acontece porque o Unmarshal() precisa modificar a struct original, preenchendo seus campos com as informações encontradas no JSON. Observe também que usamos crases para declarar o texto:

dados := `{"Nome":"Carlos","Idade":30}`

Em Go, as crases permitem criar strings que preservam o conteúdo literal, sem precisar escapar as aspas duplas do JSON.



Tags JSON em structs​


As tags JSON permitem definir como os campos de uma struct serão representados durante a conversão para JSON. Normalmente, os campos exportados de uma struct começam com letra maiúscula, mas podemos querer que suas chaves no JSON apareçam em letras minúsculas. Para isso, adicionamos uma tag depois do tipo de cada campo.

type Usuario struct {
Nome string `json:"nome"`
Idade int `json:"idade"`
Ativo bool `json:"ativo"`
}

Agora, quando executamos json.Marshal(), o resultado será parecido com:

{
"nome": "Ana",
"idade": 25,
"ativo": true
}

As tags também são consideradas durante o Unmarshal(), permitindo relacionar as chaves do JSON com os campos da struct. Também podemos usar a opção omitempty, que permite omitir um campo durante o Marshal() quando ele possui o valor zero do seu tipo.

type Usuario struct {
Nome string `json:"nome"`
Idade int `json:"idade,omitempty"`
}

Se a idade não for informada, seu valor será 0 e o campo não aparecerá no JSON.

usuario := Usuario{
Nome: "Ana",
}

Resultado:

{"nome":"Ana"}

Outra opção é json:"-", que faz com que o campo seja ignorado durante as conversões.

type Usuario struct {
Nome string `json:"nome"`
Senha string `json:"-"`
}

Nesse caso, o campo Senha não será incluído no JSON gerado pelo Marshal() e também não será preenchido pelo Unmarshal(). Isso é útil quando temos campos que não devem fazer parte da representação JSON, como informações internas ou dados sensíveis.



Tratando erros de conversão​


As funções json.Marshal() e json.Unmarshal() retornam um error, permitindo identificar problemas durante a conversão dos dados. Por exemplo, se tentarmos converter um JSON com um tipo incompatível com o campo da struct, o Unmarshal() retornará um erro.

package main

import (
"encoding/json"
"fmt"
)

type Usuario struct {
Nome string `json:"nome"`
Idade int `json:"idade"`
}

func main() {
// O campo idade deveria receber um número.
dados := `{"nome":"Ana","idade":"vinte"}`

var usuario Usuario

err := json.Unmarshal([]byte(dados), &usuario)

if err != nil {
fmt.Println("Erro ao converter JSON:", err)
return
}

fmt.Println(usuario)
}

Nesse exemplo, o campo Idade foi declarado como int, mas o JSON contém uma string com o valor "vinte". Como o Go não consegue converter esse texto diretamente para um inteiro, o Unmarshal() retorna um erro. Também podemos encontrar erros quando o JSON possui uma estrutura inválida, como aspas ausentes ou chaves que não foram fechadas corretamente.


Por padrão, o Unmarshal() ignora chaves do JSON que não possuem um campo correspondente na struct. Além disso, quando um campo não aparece no JSON, ele mantém o valor que já estava na variável, que será o valor zero do tipo se a struct tiver acabado de ser declarada. Já o Marshal() também pode retornar erros quando tentamos converter tipos que o JSON não consegue representar, como funções e channels. Por isso, devemos verificar o erro retornado pelas duas funções, principalmente quando trabalhamos com dados recebidos de arquivos ou APIs.