Čísla v JSON: zápis, přesnost a velká čísla

JSON zná jediný číselný typ a o jeho přesnosti nic neříká. Kapitola vysvětluje, jak se čísla zapisují, proč v JSON nejsou NaN ani Infinity, kde končí přesnost běžných parserů a jak bezpečně přenášet velká ID a peněžní částky.

Autor: Aktualizováno

Jak se číslo zapisuje

Číslo v JSON je vždy desítkové. Má celou část, před kterou smí stát mínus, volitelnou desetinnou část za tečkou a volitelný exponent za písmenem e nebo E. Úvodní nuly, znaménko plus před celou hodnotou, desetinná čárka ani šestnáctkový zápis povolené nejsou. Plus je naopak povolené u exponentu, například v 1e+2. Gramatika z RFC 8259:

RFC 8259, § 6
number = [ minus ] int [ frac ] [ exp ]
int    = zero / ( digit1-9 *DIGIT )
frac   = decimal-point 1*DIGIT
exp    = e [ minus / plus ] 1*DIGIT

Ukázky ověřené parserem:

ZápisPlatné číslo JSON?PročNástroj
0anonulaVyzkoušet
-12anozáporné celé čísloVyzkoušet
3.14anodesetinná tečkaVyzkoušet
1e3anoexponent, e i EVyzkoušet
-2.5E-4anoexponent se znaménkemVyzkoušet
012neúvodní nulaVyzkoušet
+1neznaménko plus před číslemVyzkoušet
.5nechybí číslice před tečkouVyzkoušet
1.nechybí číslice za tečkouVyzkoušet
3,14nedesetinná čárkaVyzkoušet
0x1Fnešestnáctkový zápisVyzkoušet
NaNneNaN a Infinity v JSON nejsouVyzkoušet

NaN a Infinity v JSON nejsou

Hodnoty, které gramatika nepopisuje, tedy NaN, Infinity a -Infinity, v JSON nejsou povolené. Knihovny se s nimi při zápisu vypořádají různě; záleží i na nastavení knihovny:

JazykZápis NaN nebo Infinity do JSON
JavaScriptJSON.stringify({ x: NaN }) tiše vrátí {"x":null}
Pythonjson.dumps(float("nan")) vrátí NaN, tedy neplatný JSON; s allow_nan=False vyhodí ValueError
PHPjson_encode(NAN) vrátí false a chybu „Inf and NaN cannot be JSON encoded“

Python tak vyrobí soubor, který JavaScript ani PHP nenačtou, a JavaScript nenápadně ztratí informaci. Pokud potřebuješ chybějící nebo neplatnou hodnotu, použij null nebo samostatný příznak, třeba "stav": "nezmereno".

Přesnost: hranice 253

Specifikace přesnost ani rozsah čísel neurčuje a dovoluje parserům stanovit si vlastní limity. Většina knihoven ale převádí čísla na double (IEEE 754 binary64), a RFC 8259 proto uvádí rozsah celých čísel, na jejichž přesné hodnotě se tyto implementace shodnou: ±(253 − 1), tedy do 9 007 199 254 740 991. I některá větší celá čísla lze v double zapsat přesně, ale ne všechna sousední čísla. Ukázky rozdílů mezi parsery (Node.js 25, Python 3.14 a PHP 8.4):

VstupJavaScriptPythonPHP
9007199254740993900719925474099290071992547409939007199254740993
1E400InfinityinfINF
NaNchybananchyba

JavaScript číslo 9007199254740993 potichu zaokrouhlí, Python a PHP ho uloží jako celé číslo přesně. PHP na 64bitové platformě má ale strop 64bitového celého čísla; nad ním převádí na float, pokud nepoužiješ JSON_BIGINT_AS_STRING:

JavaScript
JSON.parse('{"id": 9007199254740993}').id;   // 9007199254740992 (o 1 méně)
Number.MAX_SAFE_INTEGER;                        // 9007199254740991
0.1 + 0.2;                                      // 0.30000000000000004
PHP
json_decode('12345678901234567890');                            // float(1.2345678901234567E+19)
json_decode('12345678901234567890', false, 512, JSON_BIGINT_AS_STRING); // "12345678901234567890"

Číslo zapsané s exponentem nebo desetinnou tečkou navíc Python i PHP vrátí jako desetinné: 1e2 je v obou 100.0, v JavaScriptu 100. Typ výsledku tedy závisí i na tom, jak je číslo zapsané.

Velká ID a peněžní částky jako řetězce

Pro hodnoty, u kterých záleží na každé číslici, doporučuje profil I-JSON (RFC 7493) zápis jako řetězec. Typicky jde o 64bitová ID z databáze, čísla účtů nebo částky, u kterých nesmí vzniknout zaokrouhlovací chyba:

platba.json
{
  "id": "9007199254740993",
  "castka": "1499.90",
  "mena": "CZK"
}

Příjemce musí znát význam řetězce. Částku může načíst do přesného desetinného typu; identifikátor obvykle ponechá jako text. Pravidla má uvádět dokumentace a schéma API. Druhou možností u částek je celé číslo v nejmenší jednotce (haléře, centy). Pokud musíš desetinné číslo načíst přesně, některé knihovny to umí přímo:

Python
import json
from decimal import Decimal

data = json.loads('{"castka": 1499.90}', parse_float=Decimal)
data['castka']        # Decimal('1499.90'), přesně

Doporučené postupy

  • Celá čísla nad 9 007 199 254 740 991 a ID z databáze posílej jako řetězce.
  • Peněžní částky přenášej jako řetězec, nebo jako celé číslo v haléřích či centech.
  • Nikdy nezapisuj NaN ani Infinity; v Pythonu zapni allow_nan=False, ať se chyba projeví hned.
  • Nespoléhej na rozdíl mezi 1 a 1.0; jako typ ho nemusí zachovat žádný parser.
  • Rozsah a formát čísel popiš v JSON Schema (type, minimum, maximum, multipleOf).