API

Формати даних: JSON, XML, TOML та бінарні формати

Глибокий розбір форматів передачі даних у HTTP API: синтаксис JSON, порівняння з XML та TOML, бінарні формати (Protobuf, FlatBuffers), стратегії стиснення (gzip, brotli, deflate).

Формати даних: JSON, XML, TOML та бінарні формати

У попередній статті ми визначили, що API — це контракт між клієнтом і сервером. Але контракт має бути записаний якоюсь мовою. У цій статті ми вивчимо «мови», якими клієнт і сервер обмінюються даними — від людиночитаємого JSON до надшвидких бінарних форматів.

1. Проблема: Як передати об'єкт по мережі?

Уявімо, що наш сервіс замовлення кави обробляє запит і формує об'єкт у пам'яті:

Program.cs
var order = new
{
    Id = 42,
    Recipe = "lungo",
    Volume = "300ml",
    Price = new { Amount = 10.23m, Currency = "UAH" },
    CreatedAt = DateTime.UtcNow
};

Цей об'єкт живе у пам'яті .NET-процесу. Але клієнт — мобільний додаток на Swift або Kotlin — працює в абсолютно іншому середовищі. Він не може прочитати пам'ять .NET.

Потрібен спосіб перетворити об'єкт у послідовність байтів (або символів), яку можна:

  1. Передати по мережі (через HTTP).
  2. Прочитати на іншому боці, в іншій мові програмування.
  3. Відновити до структури даних нативної для тієї мови.

Цей процес називається серіалізація (Serialization) — перетворення об'єкта у формат для передачі — та десеріалізація (Deserialization) — зворотне відновлення.

Loading diagram...
graph LR
    A["C# об'єкт<br/>{ Id = 42, Recipe = &quot;lungo&quot; }"] -->|"Серіалізація"| B["JSON рядок<br/>{&quot;id&quot;:42,&quot;recipe&quot;:&quot;lungo&quot;}"]
    B -->|"HTTP передача"| C["Мережа"]
    C -->|"Десеріалізація"| D["Kotlin об'єкт<br/>Order(id=42, recipe=&quot;lungo&quot;)"]

    style A fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B fill:#f59e0b,stroke:#b45309,color:#ffffff
    style C fill:#64748b,stroke:#334155,color:#ffffff
    style D fill:#3b82f6,stroke:#1d4ed8,color:#ffffff

Питання — у якому форматі серіалізувати? Від цього вибору залежить швидкість, зручність розробки та сумісність систем.


2. JSON — формат за замовчуванням

JSON (JavaScript Object Notation) — це текстовий формат серіалізації даних, який став стандартом де-факто для HTTP API. Попри назву, JSON давно не прив'язаний до JavaScript і використовується у всіх мовах програмування.

Синтаксис JSON

JSON підтримує шість типів даних:

Приклад JSON-документа
{
  "id": 42,
  "recipe": "lungo",
  "is_ready": false,
  "volume_ml": 300,
  "price": 10.23,
  "ingredients": ["water", "coffee"],
  "machine": {
    "id": "cm-001",
    "location": "Київ, вул. Хрещатик, 1"
  },
  "completed_at": null
}
ТипПрикладПримітка
Рядок (string)"lungo"Завжди у подвійних лапках
Число (number)42, 10.23Без розділення на int/float
Булевий (boolean)true, falseТільки lowercase
NullnullВідсутність значення
Масив (array)[1, 2, 3]Упорядкований набір
Об'єкт (object){"key": "value"}Неупорядкований набір пар
Критична увага до чисел: JSON не розрізняє цілі числа та числа з плаваючою крапкою — все це просто number. Стандарт також не обмежує довжину чисел, але на практиці більшість парсерів використовують 64-бітний double, що дає лише ~15–17 значущих десяткових цифр. Для грошових сум та інших точних дробових чисел завжди використовуйте рядки або цілі числа з множником (наприклад, передавайте суму в копійках):
{
  "price_wrong": 10.23,
  "price_correct": "10.23",
  "price_as_cents": 1023
}
Це правило прямо з книги Константинова (правило 22: «Зберігайте точність дробових чисел»).

Серіалізація JSON у Minimal API

ASP.NET Core Minimal API має вбудовану серіалізацію JSON за допомогою System.Text.Json:

Program.cs
var app = WebApplication.Create(args);

app.MapGet("/v1/orders/{id}", (int id) =>
{
    // ASP.NET Core автоматично серіалізує
    // анонімний об'єкт у JSON
    return Results.Ok(new
    {
        id,
        recipe = "lungo",
        volume_ml = 300,
        price = "10.23",   // рядок для точності!
        currency = "UAH",
        is_ready = false
    });
});

app.Run();

Відповідь сервера:

HTTP/1.1 200 OK
Content-Type: application/json

{"id":1,"recipe":"lungo","volume_ml":300,"price":"10.23","currency":"UAH","is_ready":false}
Чому Results.Ok() а не просто return object? Метод Results.Ok() явно задає статус-код 200 OK і серіалізує об'єкт у JSON. Ви також можете повернути об'єкт напряму — Minimal API серіалізує його автоматично — але явний Results.Ok() робить код читабельнішим і дозволяє контролювати статус-коди.

Переваги JSON

Чому саме JSON став стандартом для HTTP API, а не, скажем, XML чи бінарний формат?

👁️ Людиночитаємість

JSON текстовий і не потребує додаткової розшифровки. Імена полів включені у сам документ. Ви відкриваєте відповідь API і одразу бачите структуру даних.

📐 Формальність

JSON немає конструкцій, які можна по-різному інтерпретувати в різних мовах. Масиви, об'єкти, рядки, числа — це нативні структури будь-якої мови програмування.

🌍 Універсальність

JSON підтримується в кожній мові та кожному фреймворку. Розробник Python, Go, Java, C# — усі знають JSON.

🪶 Легкість

Порівняно з XML, JSON значно компактніший за рахунок відсутності закриваючих тегів та атрибутів.

3. XML — на випадок, якщо зустрінете

XML (eXtensible Markup Language) — це формат, який домінував у API до ери JSON. Сьогодні XML у нових API зустрічається рідко, але ви точно стикнетесь з ним у SOAP-сервісах, корпоративних інтеграціях та конфігураційних файлах.

Той самий об'єкт у XML
<?xml version="1.0" encoding="UTF-8"?>
<order>
  <id>42</id>
  <recipe>lungo</recipe>
  <isReady>false</isReady>
  <volumeMl>300</volumeMl>
  <price currency="UAH">10.23</price>
  <ingredients>
    <ingredient>water</ingredient>
    <ingredient>coffee</ingredient>
  </ingredients>
  <machine>
    <id>cm-001</id>
    <location>Київ, вул. Хрещатик, 1</location>
  </machine>
</order>

JSON vs XML: Порівняння

АспектJSONXML
ЧитабельністьВисокаСередня (багато тегів)
РозмірКомпактнийВеликий (закриваючі теги)
ТипізаціяСлабка (number, string, bool, null)Через XML Schema (потужна, але складна)
Коментарі❌ Не підтримує<!-- коментар -->
Атрибути❌ Тільки пари ключ-значення<price currency="UAH">
Простір імен❌ Немаєxmlns: (для уникнення колізій)
Парсинг у браузеріНативна підтримка (JSON.parse)Потребує DOMParser
Чому XML програв? Не тому, що він «поганий» — XML має потужні можливості (схеми, простори імен, XSLT-трансформації). Але для типового API ці можливості надлишкові, а «ціна» в байтах — занадто висока. JSON виграв завдяки простоті та достатності для 95% задач.

4. TOML — конфігурації та метадані

TOML (Tom's Obvious Minimal Language) — це формат, спроєктований спеціально для конфігураційних файлів. Він не використовується для передачі даних між клієнтом і сервером у HTTP API, але часто зустрічається в конфігурації сервісів, CI/CD та інструментів розробки.

Приклад TOML-файла
# Це конфігурація API-сервера
[server]
host = "0.0.0.0"
port = 5000
environment = "production"

[database]
connection_string = "Host=localhost;Database=coffee"
max_pool_size = 20
enable_logging = false

[rate_limiting]
requests_per_minute = 100
burst_size = 20

# Масив таблиць — список ендпоінтів
[[endpoints]]
path = "/v1/orders"
methods = ["GET", "POST"]
auth_required = true

[[endpoints]]
path = "/v1/recipes"
methods = ["GET"]
auth_required = false

Чому TOML, а не JSON?

АспектJSONTOML
Коментарі❌ Не підтримує# коментар
ДатиРядок ("2024-01-15")Нативний тип (2024-01-15T10:30:00Z)
Багаторядкові рядки❌ Потрібно \n"""..."""
Де використовуватиAPI, обмін данимиКонфігурації, метадані
Де ви зустрінете TOML:
  • Cargo.toml (Rust)
  • pyproject.toml (Python)
  • Конфігурації CI/CD (GitLab CI, тощо)
  • Файли налаштувань сервісів
У контексті HTTP API TOML може використовуватися для конфігурації самого API-сервера, але не для передачі даних між клієнтом і сервером.

5. Бінарні формати: Protobuf, FlatBuffers, Avro

Текстові формати (JSON, XML) зручні для людей, але не оптимальні для машин. Коли кожний мілісекунда на рахунку — на допомогу приходять бінарні формати серіалізації.

Protocol Buffers (Protobuf)

Protocol Buffers — це бінарний формат від Google, який є основою gRPC. Дані описуються через .proto-файл (схему), після чого генерується код для серіалізації/десеріалізації.

order.proto
syntax = "proto3";

message Order {
  int32 id = 1;
  string recipe = 2;
  int32 volume_ml = 3;
  string price = 4;
  string currency = 5;
  bool is_ready = 6;
}

Цей файл компілюється у C#-класи, які автоматично серіалізують/десеріалізують дані у компактний бінарний формат.

Порівняння форматів

АспектJSONProtobufFlatBuffers
ТипТекстовийБінарнийБінарний
Людиночитаємий✅ Так❌ Ні❌ Ні
Потребує схему❌ Ні.proto-файл.fbs-файл
Розмір (без стискання)ВеликийМалийНайменший
Швидкість парсингуСередняВисокаНайвища (zero-copy)
КодогенераціяНеобов'язковаОбов'язковаОбов'язкова
Важливе зауваження з книги Константинова: повідомлення у форматі Protobuf неможливо прочитати без .proto-файлу. Натомість JSON-документ майже завжди можна зрозуміти, просто переглянувши його. Саме тому JSON є вибором за замовчуванням для публічних API. Бінарні формати доцільні для внутрішніх мікросервісних комунікацій, де продуктивність критична, а обидві сторони контролюються однією командою.

6. Стиснення JSON: gzip, brotli, deflate

Частий аргумент проти JSON — його надлишковість: імена полів повторюються для кожного об'єкта в масиві, багато технічних символів (лапки, дужки, коми). Але цей аргумент має важливе застереження: він справедливий лише якщо ми не використовуємо стиснення.

Як працює стиснення в HTTP

Протокол HTTP має вбудовану підтримку стиснення через заголовки Accept-Encoding та Content-Encoding:

Loading diagram...
sequenceDiagram
    participant C as 📱 Клієнт
    participant S as 🖥️ Сервер

    C->>S: GET /v1/orders<br/>Accept-Encoding: gzip, br
    Note over S: Сервер стискає відповідь<br/>обраним алгоритмом
    S-->>C: 200 OK<br/>Content-Encoding: br<br/>(стиснені дані)
    Note over C: Клієнт розпаковує<br/>автоматично

Алгоритми стиснення

gzip — найпоширеніший алгоритм стиснення для HTTP. Підтримується усіма браузерами та HTTP-клієнтами. Стискає текстовий JSON у 5–10 разів.

Увімкнення gzip у Minimal API
var builder = WebApplication.CreateBuilder(args);

// Додаємо middleware стиснення відповідей
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
});

var app = builder.Build();
app.UseResponseCompression();

app.MapGet("/v1/orders", () =>
{
    // Відповідь буде автоматично стиснута,
    // якщо клієнт надіслав Accept-Encoding: gzip
    return Results.Ok(new[] 
    {
        new { id = 1, recipe = "lungo" },
        new { id = 2, recipe = "americano" },
        // ... тисячі замовлень
    });
});

app.Run();

Ефективність стиснення: числа

Дослідження показують, що gzip практично нівелює різницю у розмірі між JSON та бінарними форматами (Protobuf):

ФорматБез стисненняgzipBrotli
JSON (100 замовлень)~45 KB~4.5 KB~3.8 KB
Protobuf (100 замовлень)~12 KB~4.0 KB~3.5 KB
Різниця3.7x~10%~8%
Висновок з книги Константинова: різниця 8-10% після стиснення — це мізер для абсолютної більшості API. Реальний виграш від переходу на бінарні формати виявиться незначним. Обирайте Protobuf або FlatBuffers лише тоді, коли ваш сервіс обробляє сотні тисяч запитів на секунду і кожен байт на рахунку. Для інших випадків — JSON + gzip/brotli більш ніж достатньо.

Високопродуктивний парсинг: simdjson

Ще один аргумент проти JSON — «повільна десеріалізація». Стандартні парсери дійсно програють бінарним форматам. Але існують оптимізовані парсери, такі як simdjson, які використовують SIMD-інструкції процесора для паралельного аналізу JSON.

simdjson здатний парсити гігабайти JSON за секунду — продуктивність, якої не вистачить лише екзотичним високочастотним системам.

Комбінація gzip/brotli + оптимізований парсер робить JSON конкурентоспроможним навіть з бінарними форматами у більшості реальних сценаріїв. Це не означає, що бінарні формати непотрібні — але їхня перевага проявляється лише у крайніх випадках.

7. Content Negotiation: як клієнт і сервер домовляються

HTTP має вбудований механізм для узгодження формату даних — Content Negotiation. Клієнт повідомляє серверу, які формати він розуміє, через заголовок Accept:

GET /v1/orders/42 HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, br

Сервер відповідає у запрошеному форматі та вказує його у Content-Type:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Encoding: br

{"id": 42, "recipe": "lungo"}

У Minimal API це працює автоматично для JSON. Якщо вам потрібно підтримати кілька форматів:

Підтримка JSON та XML
var builder = WebApplication.CreateBuilder(args);

// Додаємо підтримку XML-серіалізації
builder.Services.AddEndpointsApiExplorer();

var app = builder.Build();

app.MapGet("/v1/orders/{id}", (int id, HttpContext ctx) =>
{
    var order = new OrderResponse(id, "lungo", 300);

    // Перевіряємо, що хоче клієнт
    var accept = ctx.Request.Headers.Accept
        .FirstOrDefault() ?? "";

    if (accept.Contains("application/xml"))
    {
        // Повертаємо XML
        return Results.Content(
            $"<order><id>{id}</id></order>",
            "application/xml"
        );
    }

    // За замовчуванням — JSON
    return Results.Ok(order);
});

app.Run();

record OrderResponse(int Id, string Recipe, int VolumeMl);
Порада: у 99% сучасних API достатньо підтримувати лише JSON. Підтримка XML має сенс лише для зворотної сумісності зі старими клієнтами або корпоративними інтеграціями (SOAP).

8. Практичні завдання

Рівень 1: Базовий

Рівень 2: Порівняльний

Рівень 3: Проєктування


9. Резюме

JSON — стандарт для HTTP API

Людиночитаємий, формальний, універсальний. Єдиний формат, за яким можна зрозуміти структуру даних без зовнішньої схеми.

Гроші — тільки рядки

Дробові числа у JSON мають обмежену точність. Для грошових сум використовуйте рядки або цілі числа з множником.

Стиснення вирівнює шанси

gzip/brotli зменшують різницю між JSON та бінарними форматами до 8–10%. Для більшості API цього достатньо.

Формат = контекст

JSON для публічних API, Protobuf/gRPC для внутрішніх high-performance сервісів, TOML для конфігурацій, XML для legacy-інтеграцій.

Далі: у наступній статті ми заглибимось у парадигми клієнт-серверних API — від RPC першого покоління до REST та сучасних технологій (gRPC, GraphQL). Розберемо, чому термін «REST API» неточний і що насправді означає «HTTP API».

Copyright © 2026