Модуль 5 · Урок 18 із 58

JSON і CSV: schema перед business logic

Парсер підтверджує лише синтаксис формату. Успішний json.load або csv.DictReader ще не доводить required fields, types, allowed values, uniqueness чи business grain. Надійний pipeline розділяє decode, structural validation, normalization і domain rules.

jsoncsvschemaserialization

Безпечна практика модуля 5

Пакет містить тільки synthetic JSON/CSV, explicit UTF-8, schema validation, deterministic output, safe logging і локальні tests. Жодних персональних чи production-даних, secrets, мережевих викликів або зовнішніх залежностей.

Завантажити практичний пакет →

JSON: decode не дорівнює validation

import json
from pathlib import Path

with Path("records.json").open(encoding="utf-8") as handle:
    payload = json.load(handle)

if not isinstance(payload, list):
    raise ValueError("root must be a list")

json.load перетворює document на Python values, але не знає ваш domain. Після root type перевірте shape кожного record, required/unknown fields, value types, allowed labels і duplicate IDs. Не десеріалізуйте недовірені дані через pickle: такий payload може виконувати code.

Serialization потребує stable policy

json.dumps(
    report,
    ensure_ascii=False,
    indent=2,
    sort_keys=True,
) + "\n"

ensure_ascii=False зберігає український текст читабельним у UTF-8; sort_keys=True може стабілізувати object-key output, але не виправляє nondeterministic list order. JSON object keys є strings; custom types на кшталт Path або datetime потребують explicit conversion contract.

CSV — tabular contract із newline=””

import csv

with Path("records.csv").open(
    encoding="utf-8",
    newline="",
) as handle:
    reader = csv.DictReader(handle)
    rows = list(reader)

Документація csv вимагає відкривати file з newline="", щоб module correctly handled newlines. DictReader повертає string values: число "75" не стає int автоматично. Conversion має повертати exact field/row error, а не загальний «bad file».

Header — частина schema

Перевірте exact required headers, optional set, unknown columns, blank names і duplicates. Duplicate CSV headers небезпечні: mapping representation може втратити одне value ще до business validation. Якщо порядок columns є public export contract, зафіксуйте fieldnames явно.

required = {"id", "route", "minutes"}
headers = reader.fieldnames or []

if len(headers) != len(set(headers)):
    raise ValueError("duplicate headers")
if not required.issubset(headers):
    raise ValueError("missing required headers")

Одна normalized model для двох formats

JSON і CSV adapters мають віддати ту саму normalized record shape. Business function не повинна знати, звідки прийшли дані. Це дозволяє contract tests: однакові synthetic records у JSON і CSV дають однаковий summary; invalid fixtures — однакові reason codes.

Pipeline: bytes/text → parser → structural checks → typed normalization → domain checks → immutable-enough normalized records → deterministic report.

Definition of Done

  • JSON root і record shape перевіряються до business logic.
  • CSV відкривається з UTF-8 і newline=""; headers мають exact policy.
  • Type conversion називає row/field/value без leak sensitive payload.
  • Duplicate IDs і duplicate headers не overwrite-яться тихо.
  • Output bytes стабільні для однакового normalized input.

Методичні джерела

Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію Python.

Практична перевірка · урок 18 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. json.load успішно повернув list of dicts. Чому business logic ще не повинна вважати payload валідним?
2. Який JSON output policy зберігає українські symbols у UTF-8 і робить object keys стабільними для snapshot review?
3. Чому недовірений external payload не можна замінити з JSON на pickle лише через зручність Python objects?