Módulo 16 - Projeto final: agenda de contatos
Salvando a agenda em JSON
12 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Converter cada Contato em dicionário e vice-versa.
- Salvar a lista de contatos em JSON com json.dump.
- Carregar o arquivo com json.load reconstruindo os objetos.
- Tratar o caso de o arquivo ainda não existir.
Ouvir o resumo desta aula
Um recap de cerca de 2 minutos na voz do Valim, para ouvir no trânsito ou na academia.
Ler a transcrição do resumo
Resumo da aula: Salvando a agenda em JSON.
Os objetivos desta aula. Converter cada Contato em dicionário e vice-versa. Salvar a lista de contatos em JSON com json.dump. Carregar o arquivo com json.load reconstruindo os objetos. Tratar o caso de o arquivo ainda não existir.
Veja o essencial, parte por parte.
Pontes entre objeto e dicionário. O JSON guarda dicionários e listas, não objetos Contato diretamente.
Salvar e carregar o arquivo. Com as pontes prontas, salvar e carregar viram métodos curtos da Agenda.
Quando o arquivo ainda não existe. Há um caso a prever: a primeira vez que o programa roda, o contatos.json ainda não existe.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
Pontes entre objeto e dicionário
O formato JSON entende tipos básicos: textos, números, listas, dicionários e alguns outros. Ele não sabe o que é um objeto Contato, que é uma classe sua. Por isso, para salvar, você precisa traduzir cada contato para algo que o JSON entenda, e o encaixe natural é um dicionário com as três chaves. Na volta, ao ler o arquivo, você recebe dicionários e precisa reconstruir os objetos Contato a partir deles. Essas duas traduções ficam melhor guardadas na própria classe Contato, que conhece os seus campos.
class Contato:
def __init__(self, nome, telefone, email=""):
self.nome = nome
self.telefone = telefone
self.email = email
def __str__(self):
return f"{self.nome} | {self.telefone} | {self.email}"
def para_dicionario(self):
return {"nome": self.nome, "telefone": self.telefone, "email": self.email}
@classmethod
def de_dicionario(cls, dados):
return cls(dados["nome"], dados["telefone"], dados.get("email", ""))para_dicionario e de_dicionario traduzem entre o objeto e o formato salvável.
para_dicionario é um método comum: pega os atributos do contato e monta um dicionário. Já de_dicionario é um classmethod, marcado com @classmethod e recebendo cls no lugar de self. Ele não age sobre um contato existente; ele cria um novo a partir de um dicionário. Usar cls em vez de escrever Contato deixa o método correto mesmo se a classe for herdada no futuro. Repare no dados.get("email", ""): se um contato antigo foi salvo sem e-mail, o get devolve a string vazia em vez de estourar um erro de chave ausente.
Salvar e carregar o arquivo
Com as pontes prontas, salvar e carregar viram métodos curtos da Agenda. Para salvar, você transforma cada contato em dicionário com uma compreensão, abre o arquivo para escrita e chama json.dump. Para carregar, abre o arquivo, chama json.load para receber a lista de dicionários e recria cada Contato com de_dicionario. Carregar é um classmethod porque devolve uma agenda nova, já preenchida com o que estava no arquivo.
import json
class Agenda:
# ... init, adicionar_contato, listar, buscar_por_nome e remover como antes ...
def salvar_json(self, caminho):
dados = [contato.para_dicionario() for contato in self.contatos]
with open(caminho, "w", encoding="utf-8") as arquivo:
json.dump(dados, arquivo, ensure_ascii=False, indent=2)
@classmethod
def carregar_json(cls, caminho):
agenda = cls()
with open(caminho, encoding="utf-8") as arquivo:
dados = json.load(arquivo)
for item in dados:
agenda.adicionar_contato(Contato.de_dicionario(item))
return agendasalvar_json grava a lista de dicionários; carregar_json a lê e reconstrói a agenda.
Dois detalhes do json.dump merecem nota. O ensure_ascii=False mantém os acentos como acentos no arquivo, em vez de virar códigos como \u00e7, o que deixa o contatos.json legível para um humano. O indent=2 formata o JSON com quebras de linha e recuo, também pela legibilidade. O with open cuida de fechar o arquivo sozinho, mesmo se der erro no meio. Na carga, o mesmo with garante o fechamento, e o laço reaproveita adicionar_contato, então a agenda carregada passa pelo mesmo caminho de uma agenda preenchida à mão.
Quando o arquivo ainda não existe
Há um caso a prever: a primeira vez que o programa roda, o contatos.json ainda não existe. Se você chamar carregar_json nessa hora, o Python levanta FileNotFoundError. Isso não é um defeito; é uma situação esperada. O jeito limpo de lidar é tentar carregar e, se o arquivo não estiver lá, começar com uma agenda vazia. O try e except cai como uma luva, capturando exatamente o FileNotFoundError e ignorando-o de propósito.
try:
agenda = Agenda.carregar_json("contatos.json")
except FileNotFoundError:
agenda = Agenda()
# A partir daqui a agenda existe, com dados ou vazia
agenda.adicionar_contato(Contato("Ana Souza", "11999998888", "ana@exemplo.com"))
agenda.salvar_json("contatos.json")
print("Salvos:", len(agenda.listar()), "contatos")Se o arquivo não existe, começa vazio; depois salva e o cria.
Esse padrão, tentar carregar e cair para o vazio, é comum em programas que guardam estado. Ele torna a primeira execução tão tranquila quanto as seguintes: sem arquivo, agenda vazia; com arquivo, agenda preenchida. Depois que você adiciona um contato e chama salvar_json, o arquivo passa a existir, e as próximas execuções o encontram. Rode o trecho acima duas vezes seguidas e observe: na segunda, o número de contatos cresce, porque a agenda carregou o que a primeira gravou. É a permanência funcionando.
Teste rápido
Por que a agenda precisa de para_dicionario e de_dicionario para usar JSON?
Perguntas frequentes
- Por que salvar cada contato como dicionário em vez do objeto?
- Porque o módulo json não sabe gravar objetos de classes suas, apenas tipos básicos como dicionários, listas, textos e números. Converter cada contato em dicionário com para_dicionario dá ao json.dump algo que ele entende. Na volta, de_dicionario recria o objeto.
- O que faz o parâmetro ensure_ascii=False?
- Mantém os acentos como acentos no arquivo. Sem ele, o padrão do json escreve caracteres acentuados como sequências de escape, tipo \u00e7 para ç. O arquivo continua válido, mas fica ilegível para uma pessoa. Como a agenda é em português, vale usar False.
- Por que carregar_json é um classmethod?
- Porque ele cria e devolve uma agenda nova, em vez de agir sobre uma já existente. Com @classmethod e cls, você escreve Agenda.carregar_json("contatos.json") e recebe uma agenda pronta. É um construtor alternativo, um jeito diferente de nascer uma agenda.
- O with open é obrigatório para mexer em arquivos?
- Não é obrigatório, mas é o jeito recomendado. Ele garante que o arquivo seja fechado ao final do bloco, mesmo se ocorrer um erro no meio. Sem o with, você teria que lembrar de chamar close manualmente, o que é fácil de esquecer e pode corromper dados.
- E se o contatos.json estiver corrompido ou com JSON inválido?
- O json.load levanta json.JSONDecodeError. Um programa robusto capturaria esse erro também, avisando o usuário e talvez começando com agenda vazia. Deixamos esse tratamento como melhoria sugerida para manter o exemplo focado no caminho principal.
- Onde o arquivo contatos.json é criado?
- Na pasta em que o programa é executado, já que passamos apenas o nome, sem caminho completo. Se quiser um local fixo, informe o caminho inteiro. Rode o programa sempre da mesma pasta para encontrar o mesmo arquivo entre as execuções.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.