Python JSON
A agenda de contatos da Ana é uma lista de dicionários: cada contato tem nome, telefones, endereço. Enquanto o programa está rodando, tudo bem. Mas quando ele termina, as variáveis somem, e com elas a agenda. Para guardar essas estruturas num arquivo e recuperá-las depois, do jeitinho que estavam, o formato mais usado no mundo é o JSON. É também a língua em que os sites e aplicativos trocam dados pela internet. E o Python fala JSON com o módulo json, que já vem instalado.
- O que é JSON e como ele se parece (e não se parece) com Python.
- A transformar um dicionário em texto JSON com
json.dumps, e o texto de volta em dicionário comjson.loads. - A deixar os acentos legíveis com
ensure_ascii=Falsee o texto arrumado comindent. - A gravar e ler arquivos
.jsoncomjson.dumpejson.load. - A tabela de tradução Python ↔ JSON, e o que muda na ida e na volta.
- Os erros reais:
JSONDecodeError, osetque não vira JSON e odumpsem arquivo.
A ideia: uma ficha que qualquer programa entende
Imagine que a Ana precisa passar a ficha de um produto para outra loja, que usa outro sistema. Se ela mandar "o dicionário do Python", o outro sistema (que pode nem ser em Python) não vai entender. Os dois precisam combinar um formato de texto, com regras fixas, que qualquer linguagem saiba escrever e ler. Esse formato é o JSON (JavaScript Object Notation, "notação de objetos do JavaScript"; o nome vem da linguagem em que ele nasceu, mas hoje é de todo mundo).
Um arquivo JSON é assim:
{
"nome": "café",
"preco": 18.9,
"estoque": 12,
"promocao": true,
"fornecedor": null
}
Parece um dicionário do Python, e quase é. As diferenças são poucas, mas são rígidas:
| No Python | No JSON |
|---|---|
Textos com aspas simples ou duplas: 'café' ou "café" | Só aspas duplas: "café" |
True, False | true, false (minúsculas) |
None | null |
Chaves de qualquer tipo imutável: {1: "a"} | Chaves só de texto: {"1": "a"} |
Vírgula sobrando no fim é permitida: [1, 2, ] | Vírgula no fim é erro |
Comentários com # | Não existem comentários |
O módulo json faz a tradução nos dois sentidos, e você quase nunca precisa escrever JSON à mão.
1. Do Python para o JSON: json.dumps
import json
produto = {"nome": "café", "preco": 18.9, "estoque": 12, "promocao": True, "fornecedor": None}
texto = json.dumps(produto)
print(texto)
print(type(texto))
$ python3 dumps1.py
{"nome": "café", "preco": 18.9, "estoque": 12, "promocao": true, "fornecedor": null}
<class 'str'>
- import json
- Traz o módulo
json. - json.dumps(produto)
- Transforma o dicionário num texto JSON. O nome vem de dump string: "despejar numa string". O s do fim é de string, e vai ajudar a lembrar a diferença para o
dumpda seção 4. - type(texto)
str: o resultado é um texto comum, que dá para gravar, mandar pela internet, mostrar.
Olhe a tradução: True virou true, None virou null, e todas as aspas são duplas. Mas o que aconteceu com o "café"? Virou café.
Os acentos: ensure_ascii=False
O é é o código da letra é, escrito só com letras e números simples. Por padrão, o dumps faz isso com toda letra que não é do alfabeto inglês básico (o ASCII), por garantia (ensure ASCII, "garantir ASCII"). Não está errado: qualquer programa que ler esse JSON vai entender "café". Mas, para um ser humano, fica ilegível. A opção ensure_ascii=False desliga isso:
import json
produto = {"nome": "café", "preco": 18.9, "estoque": 12, "promocao": True, "fornecedor": None}
print(json.dumps(produto, ensure_ascii=False))
$ python3 dumps2.py
{"nome": "café", "preco": 18.9, "estoque": 12, "promocao": true, "fornecedor": null}
Agora sim, "café". Em português, com tanto acento, use sempre ensure_ascii=False (e, ao gravar num arquivo, encoding="utf-8").
Missão: rode e veja os códigos ã, ç… no lugar dos acentos. Acrescente a opção que faz o JSON mostrar "pão de queijo" e "açúcar" do jeito que se escreve.
import json
lista = {"padaria": ["pão de queijo", "açúcar", "café"]}
print(json.dumps(lista))
import json
lista = {"padaria": ["pão de queijo", "açúcar", "café"]}
print(json.dumps(lista, ensure_ascii=False))
Arrumado para ler: indent
Numa linha só, um JSON grande vira uma sopa de letras. A opção indent ("recuo") quebra as linhas e recua cada nível com o número de espaços que você escolher:
import json
contato = {
"nome": "Bia Costa",
"telefones": ["86 99999-0001", "86 3222-0002"],
"endereco": {"rua": "Rua das Flores", "numero": 12},
"aniversario": "12/03",
}
print(json.dumps(contato, ensure_ascii=False, indent=4))
$ python3 indent.py
{
"nome": "Bia Costa",
"telefones": [
"86 99999-0001",
"86 3222-0002"
],
"endereco": {
"rua": "Rua das Flores",
"numero": 12
},
"aniversario": "12/03"
}
A lista de telefones e o dicionário do endereço ficaram cada um no seu nível, como as pastas no tree. Para guardar em arquivo que pessoas vão abrir, use indent=2 ou indent=4. Para mandar pela internet, onde ninguém lê e cada byte conta, deixe sem.
2. Do JSON para o Python: json.loads
O caminho de volta. Chegou um texto JSON (de um arquivo, de um site) e você quer um dicionário para trabalhar:
import json
texto = '{"nome": "Caio", "idade": 31, "cliente_vip": false, "apelido": null}'
dados = json.loads(texto)
print(dados)
print(type(dados))
print(dados["nome"], "tem", dados["idade"], "anos")
$ python3 loads1.py
{'nome': 'Caio', 'idade': 31, 'cliente_vip': False, 'apelido': None}
<class 'dict'>
Caio tem 31 anos
- texto = '{"nome": …}'
- Um texto Python (entre aspas simples) que guarda um JSON (com aspas duplas por dentro). As aspas simples de fora são do Python; as duplas de dentro são do JSON.
- json.loads(texto)
- Load string, "carregar de uma string": lê o texto JSON e monta o valor Python equivalente.
- dados["nome"]
- Agora é um dicionário de verdade: as chaves funcionam como em qualquer dicionário.
Repare na tradução ao contrário: false virou False e null virou None. E o print do dicionário mostra aspas simples, porque é assim que o Python mostra os seus textos.
3. A tabela de tradução
| Python | vira em JSON | e volta para o Python como |
|---|---|---|
dict | objeto {…} | dict |
list | array (lista) […] | list |
tuple | array […] | list (a tupla não volta!) |
str | texto "…" | str |
int, float | número | int, float |
True / False | true / false | True / False |
None | null | None |
chave int ({1: …}) | chave texto {"1": …} | chave str ({'1': …}) |
set, datas, objetos seus | Não têm tradução: dá TypeError (veja em Quando dá errado) | |
A ida e volta nem sempre devolve exatamente o que foi:
import json
original = {"notas": (8.5, 7.0), 1: "primeiro", "ativo": True}
texto = json.dumps(original)
print(texto)
de_volta = json.loads(texto)
print(de_volta)
print(original == de_volta)
$ python3 ida_volta.py
{"notas": [8.5, 7.0], "1": "primeiro", "ativo": true}
{'notas': [8.5, 7.0], '1': 'primeiro', 'ativo': True}
False
A tupla (8.5, 7.0) voltou como lista [8.5, 7.0], e a chave 1 (número) voltou como '1' (texto). Por isso o == deu False. O JSON não tem tupla nem chave numérica, então não tem como lembrar. Na prática: em dados que vão para JSON, use listas e chaves de texto desde o começo.
4. Com arquivos: json.dump e json.load
Agora a agenda da Ana. Para gravar num arquivo, a dupla é a mesma, sem o s: dump grava direto no arquivo aberto, load lê direto do arquivo aberto.
import json
contatos = [
{"nome": "Bia", "telefone": "86 99999-0001"},
{"nome": "Caio", "telefone": "86 99999-0002"},
]
with open("contatos.json", "w", encoding="utf-8") as arquivo:
json.dump(contatos, arquivo, ensure_ascii=False, indent=2)
print("Gravei", len(contatos), "contatos")
$ python3 gravar.py Gravei 2 contatos
- open("contatos.json", "w", encoding="utf-8")
- Abre (ou cria) o arquivo para escrita, com UTF-8 para os acentos. A extensão costumeira é
.json. - json.dump(contatos, arquivo, …)
- "Despeje
contatosnestearquivo." Dois argumentos obrigatórios: o que gravar e onde. As opções são as mesmas dodumps.
O arquivo gravado, visto no terminal:
$ cat contatos.json
[
{
"nome": "Bia",
"telefone": "86 99999-0001"
},
{
"nome": "Caio",
"telefone": "86 99999-0002"
}
]
(O json.dump não põe uma quebra de linha depois do ] final. Não faz diferença para quem lê o arquivo; só que, no terminal, o próximo $ aparece colado no ].)
Outro dia, outro programa, e a agenda volta inteira:
import json
with open("contatos.json", encoding="utf-8") as arquivo:
contatos = json.load(arquivo)
for contato in contatos:
print(contato["nome"], "-", contato["telefone"])
$ python3 ler.py Bia - 86 99999-0001 Caio - 86 99999-0002
json.load(arquivo) leu o arquivo todo e devolveu a lista de dicionários, pronta para o for.
| Função | De | Para | Lembrete |
|---|---|---|---|
json.dumps(valor) | Python | texto JSON | s de string |
json.loads(texto) | texto JSON | Python | s de string |
json.dump(valor, arquivo) | Python | arquivo | sem s: precisa do arquivo aberto em "w" |
json.load(arquivo) | arquivo | Python | sem s: precisa do arquivo aberto |
O Python tem um verificador de JSON pronto, que você chama com python3 -m json.tool. Ele lê o arquivo, reclama se houver erro e, se estiver tudo certo, mostra arrumado (com 4 espaços):
$ python3 -m json.tool contatos.json
[
{
"nome": "Bia",
"telefone": "86 99999-0001"
},
{
"nome": "Caio",
"telefone": "86 99999-0002"
}
]
Formas diferentes de gravar e ler
Você já conhece o pathlib (aula Pastas e Caminhos), e ele combina bem com o dumps/loads:
import json
from pathlib import Path
arquivo = Path("contatos.json")
contatos = json.loads(arquivo.read_text(encoding="utf-8"))
contatos.append({"nome": "Duda", "telefone": "86 99999-0003"})
arquivo.write_text(json.dumps(contatos, ensure_ascii=False, indent=2), encoding="utf-8")
print(len(contatos), "contatos na agenda")
$ python3 com_path.py 3 contatos na agenda
| Forma | Quando usar |
|---|---|
with open(…) as f: + json.dump / json.load | O jeito clássico, o que você mais vai ver |
json.loads(p.read_text(…)) / p.write_text(json.dumps(…), …) | Mais curto, quando você já usa Path |
json.dumps / json.loads sem arquivo | Quando o JSON não está em arquivo: veio da internet, vai para um banco, vai ser mostrado |
str(dicionario) | Nunca como JSON: veja abaixo |
str() não é JSON
Um erro clássico é achar que basta transformar o dicionário em texto:
import json
produto = {"nome": "café", "promocao": True}
print(str(produto))
print(json.dumps(produto, ensure_ascii=False))
$ python3 str_dict.py
{'nome': 'café', 'promocao': True}
{"nome": "café", "promocao": true}
Parecem iguais, mas a primeira linha tem aspas simples e True com maiúscula: é o jeito do Python mostrar o dicionário, e não JSON. Gravar isso num arquivo e tentar ler com json.load dá o primeiro erro da próxima seção.
Quando dá errado
JSONDecodeError
import json
texto = "{'nome': 'café', 'promocao': True}"
dados = json.loads(texto)
$ python3 aspas_simples.py
Traceback (most recent call last):
File "/home/ana/aspas_simples.py", line 4, in <module>
dados = json.loads(texto)
File "/usr/lib/python3.14/json/__init__.py", line 352, in loads
return _default_decoder.decode(s)
~~~~~~~~~~~~~~~~~~~~~~~^^^
File "/usr/lib/python3.14/json/decoder.py", line 345, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/json/decoder.py", line 361, in raw_decode
obj, end = self.scan_once(s, idx)
~~~~~~~~~~~~~~^^^^^^^^
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
Vamos por partes:
File "/home/ana/aspas_simples.py", line 4: o erro começou no seujson.loads, na linha 4.- As três partes seguintes (
json/__init__.py,json/decoder.py) são o caminho por dentro do módulojson. Pode pular. json.decoder.JSONDecodeError: "erro de decodificação de JSON", o erro de "esse texto não é um JSON válido". O nome completo diz onde ele mora; no seu código, ele se chamajson.JSONDecodeError.Expecting property name enclosed in double quotes: "esperava um nome de propriedade (uma chave) entre aspas duplas".line 1 column 2 (char 1): onde o leitor se perdeu: linha 1, coluna 2 (o caractere de número 1, contando do zero). Logo depois do{, ele achou'em vez de".
Como consertar: no JSON, só aspas duplas (e true/false/null em minúsculas). Se o texto veio de um str(dicionario), troque por json.dumps(dicionario) na hora de gravar.
import json
texto = '{"nome": "café", "estoque": 12,}'
dados = json.loads(texto)
$ python3 virgula.py
Traceback (most recent call last):
File "/home/ana/virgula.py", line 4, in <module>
dados = json.loads(texto)
File "/usr/lib/python3.14/json/__init__.py", line 352, in loads
return _default_decoder.decode(s)
~~~~~~~~~~~~~~~~~~~~~~~^^^
File "/usr/lib/python3.14/json/decoder.py", line 345, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/json/decoder.py", line 361, in raw_decode
obj, end = self.scan_once(s, idx)
~~~~~~~~~~~~~~^^^^^^^^
json.decoder.JSONDecodeError: Illegal trailing comma before end of object: line 1 column 31 (char 30)
"Vírgula no fim não permitida antes do fim do objeto", na coluna 31: a vírgula depois do 12. No Python, {"estoque": 12,} é aceito; no JSON, não. Esse erro aparece muito em arquivos .json editados à mão. Como consertar: apague a vírgula antes do } (ou do ]).
Expecting value
import json
from pathlib import Path
Path("vazio.json").write_text("", encoding="utf-8")
with open("vazio.json", encoding="utf-8") as arquivo:
dados = json.load(arquivo)
$ python3 vazio.py
Traceback (most recent call last):
File "/home/ana/vazio.py", line 7, in <module>
dados = json.load(arquivo)
File "/usr/lib/python3.14/json/__init__.py", line 298, in load
return loads(fp.read(),
cls=cls, object_hook=object_hook,
parse_float=parse_float, parse_int=parse_int,
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
File "/usr/lib/python3.14/json/__init__.py", line 352, in loads
return _default_decoder.decode(s)
~~~~~~~~~~~~~~~~~~~~~~~^^^
File "/usr/lib/python3.14/json/decoder.py", line 345, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/json/decoder.py", line 363, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
"Esperava um valor", logo na linha 1, coluna 1: o arquivo não tem nada. Um arquivo vazio não é um JSON válido (uma lista vazia seria []). Repare também que o json.load só lê o arquivo e passa o texto para o loads: é por isso que o traceback passa pelos dois. Como consertar: no primeiro uso, grave um JSON de verdade ([] ou {}); ou trate o erro, como no quadro abaixo.
set no meio dos dados: not JSON serializable
import json
turma = {"nome": "Turma A", "alunos": {"Bia", "Duda"}}
print(json.dumps(turma))
$ python3 conjunto.py
Traceback (most recent call last):
File "/home/ana/conjunto.py", line 4, in <module>
print(json.dumps(turma))
~~~~~~~~~~^^^^^^^
File "/usr/lib/python3.14/json/__init__.py", line 235, in dumps
return _default_encoder.encode(obj)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^
File "/usr/lib/python3.14/json/encoder.py", line 202, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python3.14/json/encoder.py", line 263, in iterencode
return _iterencode(o, 0)
File "/usr/lib/python3.14/json/encoder.py", line 182, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
f'is not JSON serializable')
TypeError: Object of type set is not JSON serializable
when serializing dict item 'alunos'
"Objeto do tipo set não é serializável em JSON", ou seja, não tem como virar JSON (veja a tabela da seção 3). A última linha, when serializing dict item 'alunos' ("ao serializar o item 'alunos' do dicionário"), aponta a chave culpada 3.14; no Python 3.13, ela não aparece, e você tem de procurar sozinho. Como consertar: transforme em lista antes: "alunos": sorted(alunos) (ou list(alunos)). O mesmo erro aparece com datas (Object of type datetime…): grave a data como texto, como você vai ver em Datas e Horas.
dump sem o arquivo
import json
contatos = [{"nome": "Bia"}]
json.dump(contatos)
$ python3 dump_sem.py
Traceback (most recent call last):
File "/home/ana/dump_sem.py", line 4, in <module>
json.dump(contatos)
~~~~~~~~~^^^^^^^^^^
TypeError: dump() missing 1 required positional argument: 'fp'
"dump() está sem 1 argumento obrigatório: 'fp'." O fp (file pointer) é o arquivo. Confundir dump com dumps é muito comum. Como consertar: se você queria o texto, json.dumps(contatos); se queria gravar, json.dump(contatos, arquivo) dentro de um with open(…, "w").
Quando o JSON vem de fora (de um arquivo que alguém editou, de um site), o programa não deve morrer por causa dele. O json.JSONDecodeError pode ser capturado com try/except (aula try e except), e ele traz a linha e a coluna do problema:
import json
texto = '{"nome": "café", "estoque": 12,}'
try:
dados = json.loads(texto)
except json.JSONDecodeError as erro:
print("JSON com defeito:", erro)
print("Linha", erro.lineno, "coluna", erro.colno)
$ python3 tratar.py JSON com defeito: Illegal trailing comma before end of object: line 1 column 31 (char 30) Linha 1 coluna 31
Missão: o arquivo produto.json (na segunda aba) foi escrito à mão e tem dois defeitos. Rode, leia a mensagem (linha e coluna!) e conserte o arquivo, sem mexer no programa. Ele deve mostrar Mouse sem fio custa 59.9.
import json
with open("produto.json", encoding="utf-8") as arquivo:
produto = json.load(arquivo)
print(produto["nome"], "custa", produto["preco"])
{
'nome': "Mouse sem fio",
"preco": 59.9,
"ativo": True
}
{
"nome": "Mouse sem fio",
"preco": 59.9,
"ativo": true
}
Referência rápida
| Escrita | O que faz |
|---|---|
import json | Traz o módulo |
json.dumps(valor) | Python → texto JSON |
json.loads(texto) | Texto JSON → Python |
json.dump(valor, arquivo) | Python → arquivo (aberto com "w") |
json.load(arquivo) | Arquivo → Python |
ensure_ascii=False | Acentos legíveis (café em vez de café) |
indent=2 / indent=4 | Uma informação por linha, com recuo |
sort_keys=True | Grava as chaves em ordem alfabética |
except json.JSONDecodeError as erro: | Captura um JSON inválido (erro.lineno, erro.colno) |
python3 -m json.tool arquivo.json | No terminal: confere e mostra o JSON arrumado |
| Mensagem | Causa provável |
|---|---|
Expecting property name enclosed in double quotes | Chave com aspas simples (ou um str(dicionario) no lugar de JSON) |
Illegal trailing comma before end of object (ou of array) | Vírgula sobrando antes do } ou do ] |
Expecting value: line 1 column 1 (char 0) | Texto ou arquivo vazio; ou um valor inválido, como True, None ou 'texto' |
Object of type set is not JSON serializable | Tipo sem tradução (set, data, objeto): converta antes |
dump() missing 1 required positional argument: 'fp' | Usou dump querendo dumps, ou esqueceu o arquivo |
Aparece é no lugar de é | Faltou ensure_ascii=False (não é erro, só fica feio) |
A tupla voltou como lista, a chave 1 voltou como '1' | O JSON não tem tupla nem chave numérica |
Exercícios
1. Dicionário para texto
Complete para transformar o dicionário em texto JSON, com os acentos legíveis:
2. A tradução
No JSON, o None do Python se escreve:
3. Lendo do arquivo
Complete para ler a agenda do arquivo aberto:
contatos = json.(arquivo)
4. Qual destes textos é um JSON válido?
5. json.loads(json.dumps({"pontos": (1, 2)})) devolve:
6. Qual é a diferença entre json.dump e json.dumps?
Missão final: um contato novo na agenda
Missão: o programa lê a agenda de contatos.json e acrescenta a Duda, mas esquece de gravar: a última linha relê o arquivo e só encontra 2 contatos. Grave a lista atualizada com json.dump (com ensure_ascii=False e indent=2) antes de reler.
import json
with open("contatos.json", encoding="utf-8") as arquivo:
contatos = json.load(arquivo)
contatos.append({"nome": "Duda", "telefone": "86 99999-0003"})
# grave aqui
with open("contatos.json", encoding="utf-8") as arquivo:
print(len(json.load(arquivo)), "contatos")
[
{"nome": "Bia", "telefone": "86 99999-0001"},
{"nome": "Caio", "telefone": "86 99999-0002"}
]
import json
with open("contatos.json", encoding="utf-8") as arquivo:
contatos = json.load(arquivo)
contatos.append({"nome": "Duda", "telefone": "86 99999-0003"})
with open("contatos.json", "w", encoding="utf-8") as arquivo:
json.dump(contatos, arquivo, ensure_ascii=False, indent=2)
with open("contatos.json", encoding="utf-8") as arquivo:
print(len(json.load(arquivo)), "contatos")
A agenda completa, com busca e remoção de contatos, é o Projeto: Agenda de Contatos.
Resumo
- JSON é um formato de texto para guardar e trocar dados, entendido por qualquer linguagem. Parece um dicionário, mas só aceita aspas duplas,
true/false/nulle nada de vírgula no fim. json.dumps(Python → texto) ejson.loads(texto → Python): o s é de string.json.dump(valor, arquivo)ejson.load(arquivo)trabalham direto com o arquivo aberto, sempre comencoding="utf-8".ensure_ascii=Falsedeixa os acentos legíveis;indent=2deixa o arquivo arrumado.- Tupla volta como lista, chave numérica volta como texto;
sete datas não viram JSON sem conversão. str(dicionario)não é JSON. Um JSON inválido dájson.JSONDecodeError, com a linha e a coluna do defeito.