# JSON em Python: leitura e gravação de arquivos, CSV, pathlib e codificação UTF-8

URL: https://skillaria.top/pt/guides/python/json-python-arquivos

Para trabalhar com JSON em Python há o módulo embutido json: json.load lê dados de um arquivo aberto, json.dump grava os dados num arquivo, e json.loads e json.dumps fazem o mesmo com strings. O arquivo é aberto com with open(path, encoding="utf-8"), os caminhos são montados com pathlib.Path, e as tabelas são lidas com o módulo csv.

Trabalhar com JSON em Python não exige bibliotecas externas: o módulo `json` faz parte da biblioteca padrão e transforma dicionários e listas em texto e de volta. Ao lado dele quase sempre são necessárias mais três coisas: a função `open` com a instrução `with`, o módulo `pathlib` para os caminhos e o módulo `csv` para tabelas. A seguir — tudo em ordem, com código funcional e as armadilhas em que mais se tropeça.

## Função open em Python: modos e codificação

Qualquer arquivo de texto é aberto pela função embutida `open(path, mode, encoding=...)`. Ela devolve um objeto de arquivo com os métodos `read`, `readline`, `write` e que pode ser percorrido linha a linha em um laço.

| Modo | O que faz |
| --- | --- |

| `"r"` | Leitura (padrão). Se o arquivo não existir — `FileNotFoundError` |

| `"w"` | Gravação. O arquivo é criado ou **zerado já na abertura** |

| `"a"` | Acrescentar ao fim, o conteúdo existente não é alterado |

| `"x"` | Criar um arquivo novo; se ele já existir — `FileExistsError` |

| `"b"` | Complemento do modo: trabalho com bytes (`"rb"`, `"wb"`) |

### Para que serve o with

O arquivo precisa ser fechado: caso contrário, os dados podem não ser gravados no disco, e o sistema mantém o descritor ocupado. A instrução `with` fecha o arquivo automaticamente — mesmo que dentro do bloco ocorra um erro.

```
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("Primeira linha\n")
    f.write("Segunda linha\n")

with open("notes.txt", encoding="utf-8") as f:
    for line in f:
        print(line.rstrip("\n"))
```

Percorrer o arquivo em um laço lê uma linha por vez e não carrega tudo na memória, por isso assim é possível processar até logs de gigabytes. O método `f.read()` devolve todo o conteúdo em uma única string, e `f.readlines()` — uma lista de linhas.

### Como acrescentar uma linha em um arquivo sem apagar o que existe

O erro mais comum de quem está começando é abrir o arquivo com `"w"` para «acrescentar» uma linha. O modo `"w"` apaga o arquivo no momento da abertura, antes mesmo do primeiro `write`. Para acrescentar ao fim, use `"a"`, e para ter certeza de não sobrescrever um arquivo alheio — `"x"`.

```
with open("log.txt", "a", encoding="utf-8") as f:
    f.write("novo registro\n")
```

Lembre-se de que o `write` não acrescenta quebra de linha sozinho — o `"\n"` é escrito explicitamente. Também é possível usar `print(..., file=f)`: ele coloca a quebra de linha por você.

## Codificação UTF-8: de onde vêm os «caracteres quebrados»

Até o Python 3.15 a função `open()`, sem o parâmetro `encoding`, usava a codificação do sistema. No Linux e no macOS é UTF-8, mas no Windows com localidade em português — cp1252 ou cp1251 dependendo da configuração. Um script que funcionava no servidor exibia «Ã©» no lugar de «é» no notebook de um colega, ou falhava com `UnicodeDecodeError`.

No Python 3.15, pela PEP 686, o modo UTF-8 é ativado por padrão: `open()` sem `encoding` lê e grava em UTF-8 em todos os sistemas. Para voltar ao comportamento antigo use a variável de ambiente `PYTHONUTF8=0` ou a opção `-X utf8=0`, e para pedir explicitamente a codificação do sistema — `encoding="locale"`.

```
import sys
print(sys.flags.utf8_mode)   # 1 no Python 3.15

open("legacy.txt", encoding="locale")     # codificação do sistema
open("old_excel.csv", encoding="cp1251")  # arquivo salvo em cp1251
```

O outro lado da moeda: um CSV antigo do Excel ou de um script no Windows, salvo em cp1251, agora será lido como UTF-8 e gerará `UnicodeDecodeError`. A solução é ler com `encoding="cp1251"` e regravar em UTF-8. A boa prática continua a mesma — indique `encoding` de forma explícita, e o código se comportará igual em qualquer versão.

## pathlib em Python: caminhos como objetos

Juntar caminhos como strings (`"data" + "/" + name`) é inconveniente e quebra em sistemas diferentes. O módulo `pathlib` traz a classe `Path`, em que os caminhos são unidos pelo operador `/`, e o objeto tem propriedades claras.

```
from pathlib import Path

data = Path("data")
csv_path = data / "expenses.csv"

print(csv_path.name)    # expenses.csv
print(csv_path.stem)    # expenses
print(csv_path.suffix)  # .csv
print(csv_path.parent)  # data
print(csv_path.exists())

data.mkdir(parents=True, exist_ok=True)
(data / "hello.txt").write_text("Olá", encoding="utf-8")
print((data / "hello.txt").read_text(encoding="utf-8"))

for p in data.glob("*.csv"):
    print(p)
```

Os métodos `read_text` e `write_text` substituem a combinação `open` + `with` em arquivos pequenos. Um objeto `Path` pode ser passado tanto para o `open` comum quanto para `json` e `csv` — todos eles o aceitam. Para obter um caminho relativo ao próprio script, e não à pasta atual, use `Path(__file__).parent / "data"`.

No Python 3.14 a classe `Path` ganhou os métodos `copy()` e `move()`, e para cópias simples já não é necessário o `shutil`. Na mesma versão entrou na biblioteca padrão o módulo `compression.zstd` para compressão Zstandard (PEP 784).

## JSON em Python: load, dump, loads, dumps

JSON é um formato de texto para troca de dados: é isso que devolvem as APIs, e nele se guardam configurações e relatórios. O módulo `json` faz duas operações: serialização (objeto Python → texto JSON) e desserialização (texto → objeto). São quatro funções, e a confusão entre elas é uma dúvida frequente.

| Função | O que faz |
| --- | --- |

| `json.dumps(obj)` | Objeto → string JSON |

| `json.loads(s)` | String JSON → objeto |

| `json.dump(obj, f)` | Objeto → gravar em um arquivo aberto |

| `json.load(f)` | Ler JSON de um arquivo aberto |

Para memorizar: a letra `s` no fim significa string — trabalho com texto; sem ela — trabalho com arquivo.

### Correspondência de tipos

| Python | JSON |
| --- | --- |

| `dict` | objeto `{}` |

| `list`, `tuple` | array `[]` |

| `str` | string |

| `int`, `float` | número |

| `True` / `False` | `true` / `false` |

| `None` | `null` |

Uma tupla, ao ser lida de volta, vira lista; já conjuntos, datas e `Decimal` o módulo não conhece e gera `TypeError: Object of type ... is not JSON serializable`.

### Como salvar JSON em arquivo

```
import json
from pathlib import Path

report = {
    "month": "2026-10",
    "total": 15230.5,
    "categories": {"alimentação": 8200, "transporte": 2100},
    "tags": ["casa", "trabalho"],
}

path = Path("data/report.json")
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
    json.dump(report, f, ensure_ascii=False, indent=2)
```

O parâmetro `ensure_ascii=False` mantém os acentos como estão. Sem ele, no lugar de «alimentação» aparecerá `"alimentação"` — JSON correto, mas ilegível para uma pessoa. O `indent=2` faz a formatação com recuo.

### Como ler JSON de um arquivo

```
with open("data/report.json", encoding="utf-8") as f:
    data = json.load(f)

print(data["total"])                       # 15230.5
print(data["categories"]["alimentação"])   # 8200
```

O resultado é um dicionário ou uma lista comuns, com os quais se trabalha pelos meios habituais. Se o arquivo não for JSON ou estiver truncado, será lançada a exceção `json.JSONDecodeError`, com o número da linha e a posição — ela deve ser capturada quando os dados vêm de fora.

### Análise de uma string JSON e saída formatada

```
import json

raw = '{"name": "Ana", "skills": ["python", "sql"], "active": true}'
user = json.loads(raw)
print(user["skills"][0])   # python

print(json.dumps(user, ensure_ascii=False, indent=2, sort_keys=True))
```

`sort_keys=True` ordena as chaves na saída — é cômodo para comparar arquivos no git. Para formatar rapidamente um arquivo pelo terminal existe um utilitário embutido: `python -m json.tool data/report.json`.

A resposta de uma API HTTP também é uma string JSON. A biblioteca `requests` a interpreta com o método `response.json()`, e por dentro é o mesmo `json.loads`.

### Armadilha: as chaves do JSON são sempre strings

```
d = {1: "janeiro", 2: "fevereiro"}
back = json.loads(json.dumps(d))
print(back == d)   # False
print(back)        # {'1': 'janeiro', '2': 'fevereiro'}
# back[1] -> KeyError, é preciso back["1"]

fixed = {int(k): v for k, v in back.items()}
```

No JSON as chaves de um objeto só podem ser strings, por isso chaves numéricas, depois do ciclo de serialização, viram texto. Ao ler, converta-as de volta.

### Tipos próprios: datas e Decimal

```
import json
from datetime import date

def default(obj):
    if isinstance(obj, date):
        return obj.isoformat()
    raise TypeError(f"não sei serializar {type(obj).__name__}")

print(json.dumps({"day": date(2026, 10, 1)}, default=default))
# {"day": "2026-10-01"}
```

A função passada no parâmetro `default` é chamada para cada objeto desconhecido. Em casos mais complexos é possível herdar de `json.JSONEncoder` e redefinir o método `default`.

### Como acrescentar um item a um array JSON

Um JSON não pode ser estendido no fim, como um log de texto: o arquivo precisa ser lido, alterado e gravado por inteiro.

```
path = Path("data/items.json")
items = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
items.append({"id": len(items) + 1, "title": "novo"})
path.write_text(json.dumps(items, ensure_ascii=False, indent=2), encoding="utf-8")
```

Se os registros são muitos e chegam o tempo todo, é mais prático o formato JSON Lines: um objeto JSON por linha, e o arquivo é aberto no modo `"a"`.

## CSV em Python: módulo csv

CSV é uma tabela em arquivo de texto: as linhas são registros, e os valores são separados por vírgulas. O módulo `csv` trata corretamente aspas, vírgulas dentro dos valores e quebras de linha — o que um `split(",")` manual não faz.

```
import csv

with open("data/expenses.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    total = 0.0
    for row in reader:
        total += float(row["amount"])
        print(row["date"], row["category"], row["amount"])
print("Total:", total)
```

`csv.DictReader` transforma cada linha em um dicionário, usando o cabeçalho do arquivo. Todos os valores são strings: números e datas precisam ser convertidos manualmente (`float`, `int`, `date.fromisoformat`). Um arquivo com separador «;» (assim o Excel em português salva) é lido com o parâmetro `delimiter=";"`.

```
rows = [
    {"date": "2026-10-01", "category": "alimentação", "amount": 450},
    {"date": "2026-10-02", "category": "transporte", "amount": 60},
]
with open("data/out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["date", "category", "amount"])
    writer.writeheader()
    writer.writerows(rows)
```

Na gravação é obrigatório o `newline=""`: sem ele, no Windows aparecerão linhas vazias entre os registros. Se o arquivo for abrir no Excel, use `encoding="utf-8-sig"` — a marca BOM ajuda o Excel a reconhecer o UTF-8.

## De CSV para JSON: exemplo completo

```
import csv
import json
from collections import defaultdict
from pathlib import Path

src = Path("data/expenses.csv")
dst = Path("data/report.json")

by_category = defaultdict(float)
with src.open(newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        by_category[row["category"]] += float(row["amount"])

report = {"total": sum(by_category.values()), "categories": dict(by_category)}
dst.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
print(dst.read_text(encoding="utf-8"))
```

Esse é o núcleo de qualquer script de relatório: entrada em CSV, agregação em um dicionário, saída em JSON.

## Erros típicos

- `FileNotFoundError` — o caminho é relativo e é contado a partir da pasta de execução, e não do script. Use `Path(__file__).parent`.
- O arquivo foi zerado — foi aberto com `"w"` em vez de `"a"`.
- `UnicodeDecodeError` — arquivo em outra codificação; indique-a explicitamente (`cp1251`, `utf-8-sig`).
- `TypeError: Object of type set is not JSON serializable` — converta para lista ou passe `default`.
- `KeyError` depois de carregar um JSON — chaves numéricas viraram strings.
- O arquivo do projeto foi batizado `json.py` ou `csv.py` — ele sobrepõe o módulo padrão, e `import json` importa o seu arquivo.

## Resumo

Para JSON em Python basta a biblioteca padrão: `json.load`/`json.dump` para arquivos, `loads`/`dumps` para strings, `ensure_ascii=False` e `indent` para uma saída legível. Abra arquivos com `with` e codificação explícita, monte os caminhos com `pathlib`, e leia tabelas com `csv.DictReader`. No curso de Python esse tema é praticado: o controle de gastos lê os lançamentos de um CSV e grava o relatório em JSON.

## FAQ

**Qual a diferença entre json.load e json.loads?**

O json.load lê o JSON de um objeto de arquivo aberto, enquanto o json.loads interpreta uma string. Do mesmo modo, o json.dump grava em arquivo, e o json.dumps devolve uma string.

**Como gravar acentos em JSON sem as sequências \u?**

Passe ensure_ascii=False para json.dump ou json.dumps e abra o arquivo com encoding="utf-8".

**Preciso instalar alguma biblioteca para trabalhar com JSON em Python?**

Não. O módulo json já vem com o Python; basta escrever import json.

**Dá para guardar datas ou conjuntos no JSON?**

Diretamente, não: é preciso convertê-los, por exemplo a data em string ISO com isoformat, e o conjunto em lista; ou passar uma função no parâmetro default.

**Como ler um JSON grande que não cabe na memória?**

O json.load comum lê o arquivo inteiro. Para grandes volumes, guarde os dados em JSON Lines e processe linha a linha com json.loads.


---
Source: Skillaria.Top (https://skillaria.top)
