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.

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 agenda

salvar_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.