Learning Platform
Глоссарий Troubleshooting
Урок 07.02 · 30 мин
Средний
JSONRFC 8259JSON LinesNDJSONIEEE 754Data TypesEncoding OverheadStreaming

JSON: Формат и кодирование

JSON — обзор

JSON (JavaScript Object Notation) — текстовый формат обмена данными, стандартизированный в RFC 8259 (2017). Создан Дугласом Крокфордом на основе подмножества JavaScript-синтаксиса. Де-факто стандарт для REST API, конфигурационных файлов, и обмена данными между микросервисами.

В отличие от CSV, JSON имеет систему типов и поддерживает вложенные структуры. Но за читаемость и гибкость приходится платить — encoding overhead JSON выше, чем у бинарных форматов, в 3-10 раз.

JSON vs CSV vs Binary: что предоставляет формат
СвойствоХарактеристика формата.
CSVComma-Separated Values — минимальный текстовый формат.
JSONJavaScript Object Notation — структурированный текстовый формат.
Avro/ProtobufБинарные форматы с внешней схемой.
ТипыЕсть ли встроенная система типов в формате.
CSV: нет типов. Всё — строки. Типизация при чтении (schema-on-read).
JSON: 6 типов (string, number, boolean, null, object, array). Нет integer vs float, нет date/time.
Avro: 8 примитивов + logical types (date, timestamp, decimal). Protobuf: int32/64, float, double, bool, string, bytes, enum.
ВложенностьПоддержка вложенных структур (объекты внутри объектов).
CSV: плоские таблицы. Вложенность невозможна.
JSON: произвольная вложенность объектов и массивов.
Avro: records внутри records. Protobuf: nested messages.
Self-describingНужна ли внешняя схема для декодирования.
CSV: нет схемы, нет метаданных. Нужен внешний контракт.
JSON: ключи объектов = имена полей. Типы видны из значений. Человек может прочитать.
Avro: нужна схема для декодирования (binary wire = только значения). Protobuf: нужен .proto файл.
OverheadOverhead на кодирование по сравнению с raw данными.
CSV: разделители + кавычки. ~10-20% overhead.
JSON: ключи повторяются в каждом объекте + кавычки + скобки + запятые. 3-10x от raw данных.
Бинарные форматы: минимальный overhead. Avro: ~5-15%. Protobuf: ~5-20%.

Модель данных: 6 типов

RFC 8259 определяет ровно 6 типов значений:

JSON: Дерево типов
JSON ValueJSON Value — корневой тип. Любое JSON-значение — один из 6 типов ниже. JSON документ = одно value (обычно object или array).
stringСтрока: последовательность Unicode code points в двойных кавычках. Escape-последовательности для спецсимволов. UTF-8 кодировка обязательна по RFC 8259.
numberЧисло: целое или с плавающей точкой. Нет разделения int/float. Нет NaN, Infinity, -Infinity. Нет hex/octal. Примеры: 42, 3.14, -1, 2.998e8.
booleanБулево: ровно два значения — true и false (lowercase). TRUE, True, FALSE, False — невалидны.
nullNull: единственное значение — null (lowercase). NULL, Null, None — невалидны. Означает отсутствие значения.
objectОбъект: неупорядоченная коллекция пар ключ-значение. Ключи — только строки. Дублирование ключей формально допускается RFC, но поведение undefined.
arrayМассив: упорядоченная последовательность значений. Элементы могут быть разных типов (heterogeneous). Массив может быть пустым: [].
WARNING

JSON не различает целые и вещественные числа. 42 и 42.0 — оба number. Это означает, что JSON не может гарантировать сохранение типа: записали int64 42, прочитали float64 42.0. Для финансовых данных (где 0.1 + 0.2 ≠ 0.3 в float) это критично — Avro Decimal или Protobuf fixed-point лучше.

Encoding overhead: байт-за-байтом

JSON-кодирование избыточно: ключи повторяются в каждом объекте, числа хранятся как текст, скобки и кавычки добавляют постоянный overhead:

JSON Encoding: overhead по типам
ТипJSON тип данных.
ПримерПример JSON значения.
БайтыКоличество байт в UTF-8 для этого значения.
BinaryСколько занимает то же значение в бинарном формате (Avro/Protobuf).
numberЧисло 42: два ASCII символа '4' и '2'.
Текстовое представление числа 42 — два ASCII-символа.
2 байта UTF-8 (0x34 0x32).
Avro zigzag(42)=84=0x54 — 1 байт. Protobuf varint(42)=0x2A — 1 байт.
numberЧисло 1000000: 7 ASCII символов.
Текстовое представление миллиона — 7 символов.
7 байт UTF-8.
Avro zigzag(1000000)=2000000 → 3 байта varint. Protobuf: 3 байта.
booleanBoolean true: 4 ASCII символа.
Текстовое слово true — 4 символа.
4 байта UTF-8 (0x74 0x72 0x75 0x65).
Avro/Protobuf: 1 байт (0x01 для true, 0x00 для false).
nullNull: 4 ASCII символа.
Текстовое слово null — 4 символа.
4 байта UTF-8.
Avro union index=0 → 0x00. Protobuf: поле не сериализуется (0 bytes).
stringСтрока 'hello': 7 байт (5 символов + 2 кавычки).
Строка в кавычках: открывающая, 5 символов, закрывающая.
7 байт: 2 кавычки + 5 символов.
Avro: length prefix (1 byte) + 5 bytes = 6 bytes. Protobuf: аналогично.

Но главный overhead — повторение ключей:

Repeated keys: главный источник bloat
JSON: 1000 записейВ массиве из 1000 объектов каждый объект повторяет все ключи. Ключ 'username' (10 байт с кавычками) повторяется 1000 раз = 10 KB только на один ключ.
Avro: 1000 записейAvro хранит схему один раз в header файла. Data blocks содержат только значения — без ключей, без кавычек, без скобок. Ключи не повторяются.

Числовая точность: IEEE 754 double

JSON number — это текстовое представление числа. RFC 8259 не ограничивает точность, но большинство парсеров (JavaScript, Python json, Go encoding/json) преобразуют числа в IEEE 754 double (64-bit):

IEEE 754 Double: границы точности
IEEE 754 Double-Precision64-bit floating-point: 1 бит знак, 11 бит экспонента, 52 бита мантисса. Максимальное safe integer = 2^53 = 9007199254740992. Числа больше 2^53 теряют точность.
SafeЧисла до 2^53 = 9007199254740992 представляются точно. Все целые числа в этом диапазоне сохраняются без потерь.
Unsafe9007199254740993 (2^53 + 1) не представим в double. Округляется до 9007199254740992. Два разных числа → одно значение.
ID collisionSnowflake ID, Twitter ID — 64-bit integers. При передаче через JSON и парсинге в JavaScript теряют точность. Twitter API возвращает id и id_str именно по этой причине.
DANGER

Twitter (X) API возвращает id (number) и id_str (string) для каждого твита именно из-за этой проблемы. JavaScript JSON.parse() преобразует id в IEEE 754 double, теряя последние цифры 64-bit Snowflake ID. Всегда используйте id_str для идентификаторов, или библиотеки с BigInt-поддержкой (Python: json.loads() использует arbitrary-precision int, проблемы нет).

JSON Lines (NDJSON): потоковая обработка

Обычный JSON — массив объектов [{...}, {...}, ...] — не подходит для потоковой обработки: парсер должен прочитать весь массив, чтобы разобрать структуру. JSON Lines (он же NDJSON — Newline-Delimited JSON) решает эту проблему:

JSON Array vs JSON Lines
JSON ArrayМассив объектов: открывающая скобка, объекты через запятую, закрывающая скобка. Парсер должен прочитать всё до ] для валидации. Не splittable — нельзя разбить на параллельные задачи.
JSON Lines / NDJSONОдна JSON-запись на строку, разделитель — \\n. Каждая строка — самостоятельный валидный JSON. Можно читать построчно, splitить по \\n, дописывать append-only.
JSON Lines: use cases
Structured LoggingStructured logging: каждая строка лога — JSON-объект с timestamp, level, message, context. Splunkm ELK, CloudWatch — все принимают JSON Lines.
Event StreamingEvent streaming: Kafka Connect JSON converter пишет одну запись на строку. S3 Sink Connector выгружает JSONL файлы.
ML DatasetsML Training data: наборы данных для LLM (OpenAI fine-tuning, HuggingFace datasets) часто в JSONL — каждая строка = один training example.
Parallel ProcessingПараллельная обработка: splitнуть файл по \\n, раздать фрагменты worker'ам. В отличие от CSV, \\n в JSONL всегда разделитель (JSON строки экранируют \\n как \\\\n).
Worker 1..NКаждый worker парсит свои строки независимо. Нет quoting-проблем как в CSV — JSON escape гарантирует, что \\n = конец записи.
TIP

JSON Lines splittable by design: JSON escape для newline — \n (два символа: backslash + n), а настоящий newline (0x0A) внутри JSON string невозможен. Поэтому каждый \n в файле — гарантированно разделитель записей. В отличие от CSV, где \n внутри quoted field — часть значения.

JSON в data pipelines

JSON повсеместен в data engineering, но с разными ролями на каждом этапе:

JSON в типичном data pipeline
REST API (JSON)REST API возвращает JSON. Микросервисы общаются через JSON over HTTP. Frontend получает данные в JSON. Это transport format — данные передаются, не хранятся.
Kafka TopicKafka ingestion: JSON-события попадают в топик. Можно использовать JSON без Schema Registry (schema-on-read), но нет гарантии совместимости.
S3 Landing (JSONL)Landing zone: сырые JSON файлы в S3/GCS. JSONL формат. Это временное хранение — данные конвертируются в columnar формат на следующем этапе.
ETL: JSON → ParquetETL: Spark/Flink/dbt читает JSONL, применяет schema, конвертирует в Parquet/Delta. Этап schema enforcement — ошибки типизации ловятся здесь.
Data Lake (Parquet)Аналитическое хранилище: данные в Parquet/Delta Lake. Columnar access, predicate pushdown, encoding, compression. JSON на этом этапе уже не нужен.
Spark JSON reader: ключевые опции
ОпцияПараметр Spark JSON reader.
DefaultЗначение по умолчанию.
КомментарийКогда использовать и подводные камни.
multiLinemultiLine: поддержка JSON Array (объект/массив на несколько строк). По умолчанию false = JSON Lines mode.
false = JSON Lines mode. Каждая строка файла — отдельный JSON.
true: для файлов с JSON Array ([{...}, ...]). Весь файл парсится одним executor — нет параллелизма.
schemaschema: явная схема для данных. Без неё Spark сканирует файл для infer.
Не задано — Spark выполнит schema inference (дополнительный scan).
Production: всегда задавайте schema явно. Infer может выбрать неправильный тип на неполных данных.
corruptRecordcolumnNameOfCorruptRecord: столбец для записей, не прошедших парсинг.
_corrupt_record. Malformed JSON попадает в этот столбец как строка.
Проверяйте: df.filter(col('_corrupt_record').isNotNull()).count(). Если > 0 — в данных мусор.

JSON Schema: валидация без типов

JSON Schema (draft 2020-12) — отдельный стандарт для описания и валидации структуры JSON-документов. Не является частью RFC 8259, но широко используется для API-контрактов:

JSON Schema: ключевые конструкции
typetype: ограничение типа значения. Допустимые значения: string, number, integer, boolean, null, object, array.
propertiesproperties: описание полей объекта. Каждое поле — вложенная JSON Schema.
requiredrequired: массив обязательных полей. Поля не в required — необязательные.
enumenum: фиксированный набор допустимых значений. Аналог enum в Avro/Protobuf, но только для валидации — не влияет на кодирование.
additionalPropertiesadditionalProperties: разрешены ли поля, не описанные в properties. false = strict mode (как Avro/Protobuf).
$ref$ref: ссылка на другую JSON Schema. Позволяет переиспользовать определения и строить модульные схемы.
NOTE

JSON Schema — валидация, не кодирование. В отличие от Avro Schema (определяет wire format) и Protobuf .proto (генерирует код), JSON Schema только проверяет структуру готового JSON. Документ всё равно кодируется как текст со всем overhead. JSON Schema используется в OpenAPI (Swagger), API Gateway validation, и CI/CD для контрактных тестов.

Размер данных: JSON vs бинарные форматы

Сравнение размеров: 1M записей User
ФорматФормат кодирования данных.
РазмерПриблизительный размер 1M записей без компрессии.
ПочемуОсновная причина размера.
JSONJSON Array с 1M объектов: ключи повторяются миллион раз.
~150 MB: ключи (60 MB) + кавычки/скобки (30 MB) + значения (60 MB). Ключи = 40% размера.
Repeated keys: 40% размера. Текстовые числа: ×2-7 от binary.
CSVCSV: без ключей (один header row), но числа текстовые.
~80 MB: header (1 строка) + значения (текст) + разделители.
Нет repeated keys, но числа текстовые.
AvroAvro: бинарные значения + schema в header.
~25 MB: zigzag integers + length-prefixed strings + schema (однократно).
Бинарные значения. Нет ключей на wire.
ParquetParquet: columnar + encoding + compression.
~8 MB: dictionary encoding для строк + delta для int + snappy compression.
Columnar + encoding + compression. 19x меньше JSON.

Итог

JSON — хороший формат транспорта: читаемый, самоописывающий, поддерживается везде. Плохой формат хранения: encoding overhead 3-10x, отсутствие числовой точности, repeated keys. В data pipelines JSON живёт на входе (API, landing zone) и должен конвертироваться в бинарный/columnar формат как можно раньше. JSON Lines — предпочтительная форма для потоковой обработки и логирования.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. JavaScript-клиент вызывает API, получает JSON: {"tweet_id": 1234567890123456789}. JSON.parse() возвращает tweet_id = 1234567890123456800. Почему значение изменилось?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5