JSON Toolkit

Přestaňte komprimovat JSON ve špatném pořadí: minify vs gzip vs brotli, měřeno

Shrnutí: Minifikace ukrojí 30–50 % ze surového JSON, ale gzip většinu této úspory smaže. Naměřená čísla napříč skutečnými tvary plus čtyři případy, kdy minifikace věci zhorší.

Středně pokročilý~7 min čteníPublikováno 2026-10-31Aktualizováno 2026-10-31
Komprese JSON: naměřené srovnání velikosti payloadu u minify, gzip a brotli

Stručně: nejprve oddělte přenosovou kompresi od strukturálního ořezu. Pokud gzip nebo brotli ještě není nasazen, opravte konfiguraci serveru – samotný gzip -6 dosahuje 94,5 % a minify pak má hodnotu jen 0,9 bodu. Plných 46,3 % minify se projeví jen tam, kde se účtují bajty lokálně: těla požadavků, 6 MB v Lambda, 1 MiB ve Firestore, nebo JSON zapisovaný na disk a do logů.

Odznak „ušetřeno 35 %“ na vašem oblíbeném minifikátoru je poctivý – měří surové bajty. Vaše API běží nad gzipem nebo brotli a ty dva smažou přesně stejnou redundanci. Vynásobte obě čísla a dostanete špatnou odpověď.

1. Rozdělte „kompresi“ na dvě různé věci

To, čemu se říká komprese JSON, jsou ve skutečnosti dvě nesouvisející operace a jejich zaměňování je začátkem každého špatného závěru.

minify (odstranění prázdných znaků) gzip / brotli (přenosová komprese)
Povaha Transformace dokumentu Transformace přenosu
Výstup Stále platný a čitelný text JSON Binární; nepoužitelný, dokud se nedekóduje
Kde se úspora projeví Disk, paměť, logy, limity velikosti Jen na drátě a v caches
Po dekódování Bajty zůstávají menší Bajty se vrací k původní velikosti
Kdo to vlastní Váš build skript, argumenty serializátoru Nginx, CDN, serverový framework

Poslední dva řádky jsou ty důležité. Limity platform se měří po dekódování: limit 6 MB pro payload v AWS Lambda, limit 1 MiB na dokument ve Firestore. Zapnutí gzipu tam nekoupí ani jediný bajt, protože oba počítají dekódovaný výsledek.

2. minify odstraňuje přesně jednu věc: strukturální prázdné znaky

RFC 8259 povoluje prázdné znaky (space, \t, \n, \r) kolem jakéhokoli strukturálního znaku. Nic jiného v dokumentu JSON není nepovinné: názvy klíčů jsou data, čísla si nechávají každou číslici a obsah řetězců je bajtově nedotknutelný.

To činí celou operaci jedním řádkem:

const minified = JSON.stringify(JSON.parse(pretty));
// RFC 8259 povoluje prázdné znaky jen kolem strukturálních znaků,
// takže mezery uvnitř řetězce jsou data, nikoli formátování
JSON.stringify(JSON.parse('{"note": "two spaces"}'));
// {"note":"two spaces"} - obě vnitřní mezery zůstávají

Za zmínku stojí tři operace, které se řadí pod „kompresi“, ale ve skutečnosti dokument mění: zahazování klíčů s hodnotou null, zaokrouhlování desetinných čísel a přeřazování klíčů. Ty dokument mění, místo aby ho přeformátovaly, takže každý nástroj nabízející „hlubokou kompresi“ potřebuje požehnání vašeho konzumenta, než ji zapnete.

Protože minify odstraňuje jen prázdné znaky, jeho přínos závisí téměř výhradně na tom, jak velkou část souboru tvoří odsazení. Počítejte s tím, že se to v tabulkách níže objeví opakovaně.

3. Měření: čtyři dokumenty, tři úrovně gzipu plus brotli

Prostředí: Node v22.13.1, zlib.gzipSync na úrovni 1 / 6 / 9, brotliCompressSync v výchozí kvalitě (11). Čtyři vzorky záměrně pokrývají tvary, které skutečně posíláte: 50 vnořených objednávek (hluboká struktura, opakované klíče), 6úrovňovou konfiguraci (dlouhé klíče, krátké hodnoty), 200řádkové pole logů (dlouhé řetězce, téměř žádné odsazení) a skutečný media-manifest.json z tohoto webu (smíšený tvar, řetězce plné URL).

Krok první: surové bajty.

Vzorek pretty (2 mezery) pretty (4 mezery) po minify minify ušetří
50 vnořených objednávek 29 551 B 40 761 B 15 881 B 46,3 %
6úrovňová konfigurace 25 450 B 38 012 B 11 679 B 54,1 %
200řádkové pole logů 14 582 B 14 982 B 13 981 B 4,1 %
media-manifest.json 25 149 B 28 114 B 21 222 B 15,6 %

Odtud pochází odznak „ušetřeno 35 %“ a nelže. Problém se objeví ve druhém kroku.

Krok druhý: zkomprimujte každou variantu a poté se ptejte, kolik minify ještě přidá.

Vzorek nad gzip -1 nad gzip -6 nad gzip -9 nad brotli
50 vnořených objednávek 21,0 % 17,0 % 8,9 % 10,6 %
6úrovňová konfigurace 74,2 % 59,1 % 58,0 % 56,4 %
200řádkové pole logů -0,7 % -0,4 % 0,6 % -0,3 %
media-manifest.json 1,5 % 1,0 % 1,0 % 1,6 %

Stejný dokument vnořených objednávek, který ztrácí 46,3 % surových bajtů, si při gzip -6 ponechá jen 17,0 % z toho. Pole logů jde dokonce do záporu: minifikace a poté komprese je mírně větší než komprese pěkných původních dat. Záporné číslo není chyba, je přesně to, co máte očekávat, a další část vysvětluje proč.

Krok třetí: celkové zmenšení proti původnímu pěknému dokumentu – číslo, které skutečně cestuje po drátě.

Vzorek jen gzip -6 minify + gzip -6 minify + brotli
50 vnořených objednávek 94,5 % 95,4 % 96,9 %
6úrovňová konfigurace 96,0 % 98,4 % 99,0 %
200řádkové pole logů 89,9 % 89,9 % 95,0 %
media-manifest.json 83,5 % 83,6 % 86,1 %

4. Proč gzip pohlcuje zásluhu minify

gzip je LZ77 následované Huffmanovým kódováním. Základní tah LZ77 je „podívej se zpět do 32KB okna, najdi opakovanou posloupnost, nahraď ji ukazatelem (vzdálenost, délka).“ Odsazení je pro tento tah nejsnazším cíllem: každá úroveň vnoření vydá stejnou posloupnost mezer a desítky sousedních řádků opakují stejnou kombinaci. Huffman poté přiřadí krátké kódy symbolům s vysokou frekvencí, takže vzor jako \n plus osm mezer – který se v jediném souboru může opakovat tisíckrát – nakonec stojí dva nebo tři bity.

Bajty, které minify smaže, jsou tedy přesně bajty, které by kompresor slisoval téměř zdarma. Dělat to sám znamená opakovat práci, ve které kompresor vyniká, a vaše 46 % se zhroutí na 17 %. To je celý příběh za tabulkou výše.

Záporný výsledek pole logů si zaslouží vlastní vysvětlení. Ten vzorek je téměř výhradně tvořen dlouhými řetězci (řádky logů, URL, base64), což znamená málo opakování a vysokou entropii – málo redundance, kterou by kompresor mohl odstranit. Zalomení řádků v pěkné verzi náhodou dávají párovači stabilní hranice. Odstraňte je a LZ77 ztratí tyto kotvy: v tomto vzorku komprese pěkných původních dat při gzip -1 dává 1 508 bajtů, zatímco minifikace a poté komprese dává 1 518 bajtů. O deset bajtů horší.

Seřazeno podle velikosti se páky řadí takto:

Páka Ušetřeno proti původnímu pěknému dokumentu (vnořené objednávky) Cena
minify 46,3 % (jen surové bajty) Čitelnost pryč; vyžaduje build krok
gzip -6 94,5 % Trochu CPU serveru; téměř nulová konfigurace
minify + gzip -6 95,4 % Obě předchozí, sečtené
minify + brotli 96,9 % Vyžaduje HTTPS a modul brotli; předkomprimovat statické soubory

Skok z 0 na 94,5 % je práce gzipu. Skok z 94,5 % na 95,4 % – devět desetin bodu – je práce minify. Tato tabulka rozhoduje o tom, kam patří vaše úsilí.

5. Úroveň komprese je skrytá proměnná

Každý argument, který předpokládá jedinou pevnou úroveň gzipu, stojí na písku, protože úroveň přímo mění marginální hodnotu minify:

Nastavení přenosu Doplňkové bajty, které minify ještě ušetří (vnořené objednávky)
gzip -1 (výchozí gzip_comp_level v Nginx) 21,0 %
gzip -6 (výchozí ve většině standardních knihoven) 17,0 %
gzip -9 8,9 %
brotli (výchozí kvalita) 10,6 %

Čím slabší přenosová komprese, tím více se minify vyplatí. Z toho plyne praktické pravidlo: na běžné instalaci Nginx, kde gzip_comp_level je stále 1, vám minify předá 21 %. Jakmile sladíte na 9 nebo přepnete na brotli, klesne to na 9–11 % a obvykle ho můžete přeskočit.

Než začnete argumentovat kterýmkoli směrem, změřte, na které úrovni skutečně jste:

# Načtěte stejnou odpověď se třemi hodnotami Accept-Encoding a srovnejte, co dorazilo
for enc in identity gzip br; do
  size=$(curl -s -o /dev/null -w '%{size_download}' \
    -H "Accept-Encoding: $enc" https://api.example.com/v1/orders)
  echo "$enc -> $size bytes"
done

Tři čísla odpovídají na tři otázky najednou: je komprese vůbec zapnutá, na jaké úrovni a kolik místa zbývá pro minify. Pokud se identity a gzip vrátí téměř shodné, komprese vůbec neúčinkuje – a věc k opravě je Nginx, nikoli váš JSON:

gzip on;
gzip_comp_level 6;
gzip_min_length 256;      # přeskočit malé odpovědi, jen rostou
gzip_types application/json application/javascript text/css;

6. Kdy se minify skutečně vyplatí

Rozhoduje jeden test: budou ušetřené bajty někde zaznamenány, nebo narazíte na limit, který počítá surové bajty? Pokud ano, projeví se plných 46 %. Pokud je odpověď „existují jen na drátě“, pak 17 % je jejich skutečná hodnota.

Za prvé, těla HTTP požadavků. Většina serverů nedekomprimuje těla požadavků a gzip je konfigurován téměř výhradně pro odpovědi. JSON, který POSTujete, obvykle nemá žádnou ochranu přenosové vrstvy, takže úspora minify přichází v plné ceně. Totéž platí pro webhook callbacky, MQTT telemetrii a WebSocket rámce, které nikdy nevyjednaly permessage-deflate.

Za druhé, kanály s tvrdým bajtovým stropem – a každý z nich počítá dekódované bajty:

Kanál Limit Může gzip pomoci?
Synchronní payload AWS Lambda 6 MB Ne, měřeno po dekódování
Jediný dokument Firestore 1 MiB Ne
Jediná zpráva Kafka (výchozí) 1 MB Ne, komprese na straně brokeru je jiná vrstva
Kvóta localStorage ~5 MB (počítáno ve znacích) Ne
URL požadavku GET Závisí na serveru, obvykle 8 KB Ne

V této kategorii není minify optimalizace, je to rozdíl mezi požadavkem, který se odešle, a požadavkem, který selže. Vkládání JSON do query stringu URL je nejostřejší případ: URL se nikdy nekomprimují a mají délkový strop, takže nejprve minifikujte a poté rozhodněte, zda vůbec potřebujete base64.

Za třetí, JSON zapisovaný na disk nebo do logů. Logy a indexy ukládají doslovné bajty bez přenosové vrstvy. Existuje výhrada, která lidi plete: minify pomáhá jen tehdy, když jste od začátku zapisovali pěkný JSON. Ten 200řádkový vzorek logů ušetřil jen 4,1 %, protože jeho řádky už byly kompaktní a odsazení tvořilo téměř nic z celého souboru. Naopak, pokud váš logger volá JSON.stringify(obj, null, 2), vypuštění třetího argumentu jsou peníze zdarma.

Poslední poznámka: když jsou surové bajty skutečně těsné, názvy klíčů jsou obvykle větší páka než odsazení. U media-manifest.json nahrazení jeho 14 jedinečných klíčů k0 až k13 sníží minifikovanou formu z 21 222 B na 18 887 B – dalších 11,0 %. Komprimované se tento zbývající náskok smrskne na 2,4 % pod gzip -6 a 1,3 % pod brotli. Stejný mechanismus jako u všeho výše: odsazení i názvy klíčů jsou opakované strukturální informace a kompresor si stejnou dividendu nárokuje v obou případech.

7. Čtyři případy, kdy to škodí

Neminifikujte soubory, které commitujete. Změňte "qty": 2 na 3 a pěkný soubor ukáže v git diff jeden čistý řádek; minifikovaný soubor přepíše celý řádek a u stovek polí změnu vůbec nenajdete. Je to stejná mechanika, kterou pokrývá Jak řešit konflikty slučování JSON – diff nástroje pracují po řádcích, takže jednořádkový JSON mění každou úpravu na „celý řádek se liší“. Udržujte v repozitáři pěkné soubory a minifikujte jen ve výstupu sestavení nebo při zápisu do úložiště.

Nekomprimujte malé odpovědi. Odpověď obsahující jen {"ok": true} má po přidání odsazení 16 bajtů a po gzip -6 36 bajtů – o dvacet bajtů více, protože slovník a hlavičky gzipu zabírají prostor. Není to teoretické; přesně proto existuje gzip_min_length. Jeho výchozí hodnota 20 bajtů v Nginx je pro produkci hluboko pod realitou, kde je obvyklá spodní hranice 256.

Nečekejte rychlejší parsování. Přiškálování stejné sady objednávek na 4 000 záznamů dává pěkný dokument 2 374 378 bajtů proti 1 282 358 minifikovaným – o 46 % menší – přesto se medián času JSON.parse posune jen z 5,7 ms na 5,0 ms, zhruba o 12 % rychleji. Poloviční velikost není poloviční čas, protože parser vydává většinu rozpočtu na krájení řetězců a převod čísel; přeskočení prázdných znaků je okrajové. Náklady parsování skutečně řídí počet klíčů a obsah řetězců, což podrobně rozebírá Parsování, vykreslování a streamování velkých souborů JSON.

Nedělejte dodatečnou minifikaci u služby, která už běží na brotli nebo gzip -9. Přínos klesne na 9–11 %, zatímco cena – trvalá ztráta čitelnosti při ladění – zůstává. Zkontrolujte místo toho, zda gzip_types vůbec pokrývá application/json. Spousta webů zapne gzip a komprimuje jen text/html, takže JSON zůstane zcela nekomprimovaný; v takové situaci není minify první krok, oprava konfigurace je.

8. Rozhodovací tabulka: který krok první

Vaše situace Udělejte nejprve toto Kde se řadí minify
Žádná přenosová komprese Nakonfigurujte gzip_types v Nginx nebo zapněte automatickou kompresi CDN Přeskočte; devět desetin přínosu se vypaří
Běží gzip -1 (výchozí Nginx) Zvyšte gzip_comp_level na 6 Druhé, má cenu 21 %
Už na brotli nebo gzip -9 Proveďte audit dlouhých klíčů, přidejte stránkování nebo ořežte pole Přeskočte; zbývá jen 9–11 %
Narážíte na 6 MB Lambda nebo 1 MiB Firestore Minifikujte přímo; počítá se plných 46 % První
Těla POST, webhooky, MQTT Minifikujte přímo; přenos se tu zřídka uplatní První
Konfigurační soubory do Gitu Nechte je pěkné Jen ve výstupu sestavení
Logy, telemetrie, offline výpisy Odstraňte argument null, 2 v loggeru To je první krok

Pravidlo v jedné větě: podívejte se, kterou bajtovou účetní knihu se snažíte zmenšit – tu zaznamenávanou lokálně (disk, kvóta, limit platformy, tělo požadavku), nebo tu, která existuje jen na drátě. Lokální znamená minify; jen na drátě znamená nejprve zapnout brotli nebo gzip.

V praxi spusťte smyčku curl výše, abyste potvrdili, že přenosová komprese běží, poté vzorek prožeňte nástrojem Minify JSON a přečtěte údaj o úspoře. Pokud je toto číslo nad 40 %, zatímco curl hlásí identity a gzip s téměř stejnou velikostí, vaším úzkým hrdlem je konfigurace serveru, nikoli JSON. Pokud jsou dvě čísla curl už blízko, tento dokument toho moc dalšího nabídnout nemůže a měli byste raději ořezávat pole nebo stránkovat – téma Základy návrhu smlouvy JSON API.

Je minifikovaný JSON stále platný JSON?

Ano. minify odstraňuje jen prázdné znaky, které RFC 8259 povoluje kolem strukturálních znaků, a nikdy nezasahuje do obsahu řetězců, takže JSON.parse produkuje hodnotu shodnou s původní. Jedinou obětí je lidská čitelnost.

brotli, nebo gzip?

Na stejném minifikovaném vzorku ušetří brotli 96,9 % a gzip -6 95,4 % – úzký rozdíl. Pro dynamické odpovědi použijte gzip -6, abyste udrželi CPU nízké, a brotli si nechte pro statické soubory doručované přes HTTPS, které můžete předkomprimovat do souborů .br.

Minifikoval jsem JSON, ale API se nezrychlilo. Proč?

Zkontrolujte odpověď pomocí curl -sI, zda má hlavičku Content-Encoding: gzip. Mnoho instalací Nginx má gzip_types ve výchozím nastavení jen text/html, takže JSON nebyl nikdy pokryt – a na drát se dostalo méně než pětina vašich 46 %.

Mají se konfigurační soubory commitovat minifikované?

Ne. Diff nástroje srovnávají řádek po řádku, takže jednořádkový JSON mění každou úpravu na „celý řádek se změnil“ a řešení konfliktů stojí mnohem víc než ušetřené bajty. Udržujte v repozitáři pěkné verze a minifikujte při sestavení.

Jak velká má být odpověď, aby se gzip vyplatil?

Obvyklý práh je 256 bajtů až 1 KB. Odpověď malá jako {"ok": true} – 16 bajtů s odsazením – naroste pod gzip -6 na 36 bajtů, protože slovník a hlavičky stojí víc, než kolik obnoví. Přesně to řídí gzip_min_length.