JSON
Nesta aula, você aprenderá a trabalhar com JSON em Python, desde os conceitos básicos de serialização e desserialização até o tratamento de objetos customizados e erros. Vamos explorar as funções json.load, json.dumps, e as melhores práticas para lidar com dados JSON em aplicações reais.
JSON (JavaScript Object Notation) é um formato leve de intercâmbio de dados, amplamente utilizado em APIs, configurações e armazenamento de dados. Em Python, o módulo json fornece funcionalidades para converter dados Python em JSON e vice-versa. Nesta aula, vamos mergulhar nas principais funções, explorar a serialização de objetos customizados e aprender a lidar com erros comuns.
Compreender JSON é essencial para qualquer desenvolvedor Python, pois é o formato padrão para comunicação entre serviços web, armazenamento de preferências e até mesmo para persistência de dados estruturados. Vamos começar com as funções mais básicas e avançar para técnicas mais avançadas.
json.load e json.dumps
A função json.dumps() (dump string) converte um objeto Python (como dicionário, lista, tupla, string, número, booleano ou None) em uma string JSON. Já json.load() (load string) faz o inverso: converte uma string JSON em um objeto Python. Essas funções são as mais utilizadas quando se trabalha com dados JSON em memória.
Vamos ver um exemplo simples:
import json
# Python dict
dados = {
"nome": "Alice",
"idade": 30,
"cidade": "São Paulo"
}
# Serializar para string JSON
json_string = json.dumps(dados)
print(json_string) # {"nome": "Alice", "idade": 30, "cidade": "São Paulo"}
# Desserializar de volta para dict
dados_recuperados = json.loads(json_string)
print(dados_recuperados) # {'nome': 'Alice', 'idade': 30, 'cidade': 'São Paulo'}
print(dados_recuperados["nome"]) # AliceNote que json.loads() é usado para strings, enquanto json.load() é usado para arquivos. Da mesma forma, json.dumps() para strings e json.dump() para arquivos. Veremos isso em detalhes na seção sobre serialização.
A função json.dumps() aceita parâmetros opcionais como indent para formatação legível, sort_keys para ordenar as chaves, e ensure_ascii para controlar a codificação de caracteres não-ASCII. Exemplo:
import json
dados = {"nome": "João", "idade": 25, "cidade": "Rio de Janeiro"}
# Formatação com indentação e chaves ordenadas
json_pretty = json.dumps(dados, indent=4, sort_keys=True)
print(json_pretty)
# Saída:
# {
# "cidade": "Rio de Janeiro",
# "idade": 25,
# "nome": "João"
# }Esses parâmetros são úteis para gerar JSON legível para humanos, por exemplo, em arquivos de configuração ou logs.
Serialização
Serialização é o processo de converter um objeto em memória em um formato que possa ser armazenado (em arquivo) ou transmitido (via rede). Em Python, a serialização para JSON é feita com json.dump() (para arquivos) e json.dumps() (para strings). A desserialização é feita com json.load() e json.loads().
Vamos ver como trabalhar com arquivos:
import json
# Dados a serializar
dados = {
"produtos": [
{"nome": "Camiseta", "preco": 49.90},
{"nome": "Calça", "preco": 89.90}
]
}
# Serializar para arquivo
with open("dados.json", "w", encoding="utf-8") as arquivo:
json.dump(dados, arquivo, ensure_ascii=False, indent=4)
# Desserializar do arquivo
with open("dados.json", "r", encoding="utf-8") as arquivo:
dados_lidos = json.load(arquivo)
print(dados_lidos)
# {'produtos': [{'nome': 'Camiseta', 'preco': 49.9}, {'nome': 'Calça', 'preco': 89.9}]}É importante usar ensure_ascii=False para preservar caracteres acentuados, e definir a codificação UTF-8 ao abrir o arquivo.
Quando serializamos objetos Python, devemos lembrar que JSON suporta apenas tipos básicos: dicionários, listas, strings, números, booleanos e null. Objetos como datetime, Decimal ou classes personalizadas não são serializáveis por padrão. Para lidar com isso, precisamos criar serializadores customizados, como veremos na próxima seção.
Objetos customizados
Para serializar objetos de classes personalizadas, precisamos fornecer uma função de serialização que converta o objeto em um dicionário ou outro tipo JSON. Isso é feito passando o parâmetro default para json.dumps() ou json.dump(). Essa função é chamada para objetos que não são serializáveis por padrão.
Vamos criar uma classe Pessoa e aprender a serializá-la:
import json
class Pessoa:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
def serializar_pessoa(obj):
if isinstance(obj, Pessoa):
return {"nome": obj.nome, "idade": obj.idade}
# Para outros objetos, podemos levantar uma exceção ou retornar None
raise TypeError(f"Tipo {type(obj)} não serializável")
p = Pessoa("Maria", 28)
json_string = json.dumps(p, default=serializar_pessoa)
print(json_string) # {"nome": "Maria", "idade": 28}Para desserializar, precisamos de uma função object_hook que recebe um dicionário e retorna um objeto da classe desejada. Exemplo:
def desserializar_pessoa(dicionario):
return Pessoa(dicionario["nome"], dicionario["idade"])
p2 = json.loads(json_string, object_hook=desserializar_pessoa)
print(p2.nome) # Maria
print(p2.idade) # 28Outra abordagem é usar a classe json.JSONEncoder e json.JSONDecoder para criar codificadores e decodificadores reutilizáveis. Isso é útil quando precisamos serializar muitos tipos de objetos. Vamos ver um exemplo:
import json
class PessoaEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Pessoa):
return {"__pessoa__": True, "nome": obj.nome, "idade": obj.idade}
return super().default(obj)
class PessoaDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
super().__init__(object_hook=self.object_hook, *args, **kwargs)
def object_hook(self, dicionario):
if "__pessoa__" in dicionario:
return Pessoa(dicionario["nome"], dicionario["idade"])
return dicionario
p = Pessoa("Carlos", 35)
json_str = json.dumps(p, cls=PessoaEncoder)
print(json_str) # {"__pessoa__": true, "nome": "Carlos", "idade": 35}
p2 = json.loads(json_str, cls=PessoaDecoder)
print(p2.nome) # CarlosEssa abordagem é mais robusta e permite marcar objetos com um identificador especial para facilitar a desserialização.
Tratamento de erros
Ao trabalhar com JSON, é comum encontrar erros de parsing ou serialização. O módulo json levanta exceções como json.JSONDecodeError (subclasse de ValueError) quando a string JSON é inválida, e TypeError quando tentamos serializar um objeto não suportado. Vamos ver como lidar com esses erros.
Para capturar erros de parsing, usamos try/except:
import json
json_invalido = '{"nome": "João"'
try:
dados = json.loads(json_invalido)
except json.JSONDecodeError as e:
print(f"Erro ao decodificar JSON: {e}")
print(f"Na linha {e.lineno}, coluna {e.colno}")
print(f"Mensagem: {e.msg}")
else:
print("JSON válido")
print(dados)Já para erros de serialização, podemos capturar TypeError:
import json
class MinhaClasse:
pass
obj = MinhaClasse()
try:
json.dumps(obj)
except TypeError as e:
print(f"Erro de serialização: {e}")
# Saída: Erro de serialização: Object of type MinhaClasse is not JSON serializableAlém disso, podemos usar o parâmetro default para evitar que a exceção seja levantada, convertendo objetos não serializáveis em algo serializável, como uma string representativa:
def default_serializer(obj):
return str(obj) # Converte para string
json_string = json.dumps(obj, default=default_serializer)
print(json_string) # "<__main__.MinhaClasse object at 0x...>"No entanto, é importante ter cuidado com essa abordagem, pois pode mascarar erros.
Boas práticas e observações finais
Ao trabalhar com JSON em Python, siga estas boas práticas:
- Sempre defina a codificação UTF-8 ao ler ou escrever arquivos JSON para suportar caracteres especiais.
- Use
ensure_ascii=Falseao serializar para preservar caracteres não-ASCII. - Para objetos customizados, implemente métodos
to_dict()efrom_dict()na própria classe, facilitando a serialização e desserialização. - Valide o JSON recebido de fontes externas antes de usá-lo, para evitar surpresas.
- Considere usar a biblioteca
pydanticoudataclassescomjsonpara validação e serialização mais robusta em projetos maiores.
JSON é um formato simples, mas poderoso. Com o módulo json do Python, você pode integrar facilmente seus programas com APIs web, arquivos de configuração e muito mais.
Referências
- Documentação oficial do módulo json
- Introdução ao JSON (site oficial)
- Tutorial de Python JSON no Real Python
- Trabalhando com JSON (MDN)
- Documentação sobre Unicode em Python
Exercícios
- Escreva um script que leia um arquivo JSON chamado
config.json(contendo um dicionário com chaves como"host"e"porta") e imprima os valores. Usejson.load(). - Dado o dicionário
dados = {"nome": "Ana", "idade": 32, "cidade": "Belo Horizonte"}, serialize-o para uma string JSON com indentação de 2 espaços e chaves em ordem alfabética. Imprima o resultado. - Crie uma classe
Produtocom atributosnomeepreco. Implemente um encoder e um decoder para essa classe, e serialize um objetoProdutopara JSON e depois desserialize de volta, exibindo os atributos. - Escreva um código que tente carregar uma string JSON inválida (por exemplo,
"{'nome': 'João'}") e capture a exceção, imprimindo uma mensagem amigável. - Dada uma lista de dicionários representando produtos, serialize a lista para um arquivo chamado
produtos.jsoncom indentação e sem caracteres ASCII escapados. Depois, leia o arquivo e exiba o número de produtos.
import json
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)
print(config["host"])
print(config["porta"])import json
dados = {"nome": "Ana", "idade": 32, "cidade": "Belo Horizonte"}
json_string = json.dumps(dados, indent=2, sort_keys=True)
print(json_string)import json
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
class ProdutoEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Produto):
return {"__produto__": True, "nome": obj.nome, "preco": obj.preco}
return super().default(obj)
class ProdutoDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
super().__init__(object_hook=self.object_hook, *args, **kwargs)
def object_hook(self, dicionario):
if "__produto__" in dicionario:
return Produto(dicionario["nome"], dicionario["preco"])
return dicionario
p = Produto("Teclado", 150.00)
json_str = json.dumps(p, cls=ProdutoEncoder)
print(json_str)
p2 = json.loads(json_str, cls=ProdutoDecoder)
print(p2.nome, p2.preco)import json
json_invalido = "{'nome': 'João'}"
try:
dados = json.loads(json_invalido)
except json.JSONDecodeError as e:
print("JSON inválido!")
print(f"Erro: {e.msg} na linha {e.lineno}, coluna {e.colno}")
else:
print(dados)import json
produtos = [
{"nome": "Camiseta", "preco": 49.90},
{"nome": "Calça", "preco": 89.90}
]
with open("produtos.json", "w", encoding="utf-8") as f:
json.dump(produtos, f, ensure_ascii=False, indent=4)
with open("produtos.json", "r", encoding="utf-8") as f:
dados = json.load(f)
print(len(dados)) # 2