JSON (JavaScript Object Notation) é um formato de dados leve e amplamente utilizado para troca de informações entre sistemas. Em Python, o módulo json fornece funcionalidades para codificar (serializar) e decodificar (desserializar) dados nesse formato. Dominar o uso de JSON é essencial para trabalhar com APIs, armazenar configurações e persistir dados estruturados.

Nesta aula, vamos explorar as principais funções do módulo json, com foco em json.load e json.dumps, além de abordar a serialização de objetos customizados e o tratamento de erros. Ao final, você será capaz de integrar JSON em seus projetos com confiança.

json.load e json.dumps

A função json.dumps (dump string) converte um objeto Python (como dicionários, listas, strings, números, booleanos e None) em uma string JSON. Já json.load (load) lê dados JSON de um arquivo ou de uma string e os converte de volta para objetos Python. A função equivalente para strings é json.loads.

Vejamos um exemplo básico de serialização com json.dumps:

import json

dados = {
    "nome": "Ana",
    "idade": 30,
    "ativo": True,
    "tags": ["python", "json"],
    "endereco": {
        "cidade": "São Paulo",
        "uf": "SP"
    }
}

# Serializando para string JSON
json_string = json.dumps(dados)
print(json_string)
# Saída: {"nome": "Ana", "idade": 30, "ativo": true, "tags": ["python", "json"], "endereco": {"cidade": "São Paulo", "uf": "SP"}}

Para ler JSON de uma string, usamos json.loads:

json_texto = '{"nome": "Ana", "idade": 30}'
dados = json.loads(json_texto)
print(dados["nome"])  # Ana

json.load é utilizado para ler diretamente de um arquivo:

with open("dados.json", "r", encoding="utf-8") as arquivo:
    dados = json.load(arquivo)
print(dados)

Da mesma forma, json.dump grava um objeto Python diretamente em um arquivo:

with open("saida.json", "w", encoding="utf-8") as arquivo:
    json.dump(dados, arquivo, ensure_ascii=False, indent=4)

O parâmetro ensure_ascii=False garante que caracteres acentuados sejam preservados, e indent=4 formata o JSON para melhor legibilidade.

Serialização

Serialização é o processo de converter um objeto em memória (como um dicionário ou uma lista) em uma representação que possa ser armazenada ou transmitida. A desserialização é o processo inverso. O módulo json suporta serialização de tipos primitivos: dict, list, tuple, str, int, float, bool e None. Tuplas são convertidas em listas JSON.

É importante notar que o JSON não suporta todos os tipos de objetos Python. Por exemplo, objetos datetime ou classes definidas pelo usuário não são serializáveis por padrão. Para lidar com isso, precisamos personalizar o processo de serialização, como veremos a seguir.

Objetos customizados

Quando precisamos serializar objetos que não são nativamente suportados, podemos criar uma função de serialização personalizada e passá-la ao parâmetro default de json.dumps. Essa função deve converter o objeto para um tipo serializável, geralmente um dicionário.

Considere a seguinte classe:

class Pessoa:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def to_dict(self):
        return {"nome": self.nome, "idade": self.idade}

Para serializar um objeto Pessoa, podemos fazer:

def default_serializer(obj):
    if hasattr(obj, "to_dict"):
        return obj.to_dict()
    raise TypeError(f"Tipo não serializável: {type(obj)}")

p = Pessoa("Carlos", 25)
json_string = json.dumps(p, default=default_serializer)
print(json_string)  # {"nome": "Carlos", "idade": 25}

Outra abordagem é usar o parâmetro default com uma função lambda:

json.dumps(p, default=lambda obj: obj.__dict__)

Para desserializar, podemos usar json.loads com o parâmetro object_hook para converter um dicionário de volta em um objeto customizado:

def decode_pessoa(dct):
    if "nome" in dct and "idade" in dct:
        return Pessoa(dct["nome"], dct["idade"])
    return dct

dados = json.loads(json_string, object_hook=decode_pessoa)
print(dados.nome)  # Carlos

Tratamento de erros

Ao trabalhar com JSON, erros podem ocorrer devido a dados malformados, tipos incompatíveis ou problemas de codificação. O módulo json lança exceções específicas que devem ser tratadas para tornar o código robusto.

As principais exceções são:

  • json.JSONDecodeError – ocorre quando a string ou arquivo não contém JSON válido.
  • TypeError – ocorre quando um objeto não é serializável.
  • FileNotFoundError – ao tentar abrir um arquivo inexistente.

Exemplo de tratamento de erro ao decodificar:

import json

texto = "{nome: 'Ana'}"  # JSON inválido (faltam aspas nas chaves)

try:
    dados = json.loads(texto)
except json.JSONDecodeError as e:
    print(f"Erro de JSON: {e}")
    print(f"Na linha {e.lineno}, coluna {e.colno}")

Para serialização, podemos capturar TypeError:

try:
    json.dumps(set([1, 2, 3]))  # set não é serializável
except TypeError as e:
    print(f"Erro de serialização: {e}")

Além disso, é possível usar json.dumps com o parâmetro skipkeys=True para ignorar chaves que não são de tipos básicos (ex.: tuplas com elementos não serializáveis) em vez de lançar erro.

Boas práticas e observações finais

Ao trabalhar com JSON em Python, siga estas boas práticas:

  • Use ensure_ascii=False para preservar caracteres acentuados.
  • Use indent=2 ou indent=4 para melhorar a legibilidade ao gravar arquivos.
  • Valide os dados recebidos de fontes externas antes de processá-los.
  • Em aplicações web, esteja ciente dos riscos de segurança ao carregar JSON de fontes não confiáveis (evite usar eval).
  • Documente a estrutura esperada dos JSONs que sua aplicação consome ou produz.

Referências

Exercícios

  1. Escreva um código que serialize um dicionário contendo uma lista de números e uma tupla de strings para JSON, e depois desserialize a string de volta para um dicionário. Imprima o tipo da tupla após a desserialização.
  2. ✓ Resposta:
    import json
    
    dados = {
        "numeros": [1, 2, 3],
        "strings": ("a", "b", "c")
    }
    
    json_string = json.dumps(dados)
    print(json_string)  # {"numeros": [1, 2, 3], "strings": ["a", "b", "c"]}
    
    decodificado = json.loads(json_string)
    print(type(decodificado["strings"]))  # <class 'list'>
    
  3. Crie uma classe Produto com atributos nome e preco. Implemente a serialização e desserialização de um objeto dessa classe usando as funções to_dict e object_hook.
  4. ✓ Resposta:
    import json
    
    class Produto:
        def __init__(self, nome, preco):
            self.nome = nome
            self.preco = preco
    
        def to_dict(self):
            return {"nome": self.nome, "preco": self.preco}
    
    def decode_produto(dct):
        if "nome" in dct and "preco" in dct:
            return Produto(dct["nome"], dct["preco"])
        return dct
    
    produto = Produto("Notebook", 2500.0)
    json_str = json.dumps(produto, default=lambda obj: obj.to_dict())
    print(json_str)  # {"nome": "Notebook", "preco": 2500.0}
    
    produto2 = json.loads(json_str, object_hook=decode_produto)
    print(produto2.nome, produto2.preco)  # Notebook 2500.0
    
  5. Escreva um programa que leia um arquivo JSON contendo uma lista de usuários (cada um com nome e email) e exiba apenas os emails. Trate possíveis erros de arquivo não encontrado e JSON inválido.
  6. ✓ Resposta:
    import json
    
    try:
        with open("usuarios.json", "r", encoding="utf-8") as f:
            usuarios = json.load(f)
        for user in usuarios:
            print(user["email"])
    except FileNotFoundError:
        print("Arquivo não encontrado.")
    except json.JSONDecodeError as e:
        print(f"Erro de JSON: {e}")
    
  7. Explique a diferença entre json.dumps e json.dump, e entre json.loads e json.load. Dê um exemplo de uso de cada um.
  8. ✓ Resposta:

    json.dumps serializa um objeto Python em uma string JSON. json.dump serializa e grava diretamente em um arquivo. json.loads desserializa uma string JSON em objeto Python. json.load lê de um arquivo e desserializa. Exemplos:

    import json
    
    dados = {"chave": "valor"}
    
    # dumps: retorna string
    s = json.dumps(dados)
    
    # dump: escreve em arquivo
    with open("dados.json", "w") as f:
        json.dump(dados, f)
    
    # loads: a partir de string
    obj = json.loads(s)
    
    # load: a partir de arquivo
    with open("dados.json", "r") as f:
        obj2 = json.load(f)
    
  9. Qual exceção é lançada quando tentamos serializar um objeto do tipo set? Como podemos contornar isso? Escreva um código que converta um set em uma lista antes da serialização.
  10. ✓ Resposta:

    A exceção é TypeError. Podemos converter o set em lista antes de serializar:

    import json
    
    meu_set = {1, 2, 3}
    try:
        json.dumps(meu_set)
    except TypeError as e:
        print(f"Erro: {e}")
    
    # Convertendo para lista
    lista = list(meu_set)
    json_str = json.dumps(lista)
    print(json_str)  # [1, 2, 3]