Chyby při převodu na JSON: circular structure, BigInt, datetime

Převod dat na JSON selže, když data obsahují něco, co JSON neumí zapsat: objekt odkazující sám na sebe, typ bez obdoby v JSON (BigInt, datetime, Decimal), NaN a Infinity nebo v PHP text v jiném kódování než UTF-8. Hlášky jako Converting circular structure to JSON, Object of type datetime is not JSON serializable nebo Malformed UTF-8 characters popisují vždy data, ne JSON.

Autor: Aktualizováno

Znění hlášky v jednotlivých prostředích

Parser Hláška Kdy
Node.js 25, Chrome (V8) JSON.stringify Converting circular structure to JSON Objekt odkazuje sám na sebe
Do not know how to serialize a BigInt Typ, který JSON nezná (BigInt, datetime, Decimal, set)
projde bez chyby NaN a Infinity
Python 3.14 json.dumps, json.dump Circular reference detected Objekt odkazuje sám na sebe
Object of type datetime is not JSON serializable Typ, který JSON nezná (BigInt, datetime, Decimal, set)
projde bez chyby NaN a Infinity
.NET 10 (System.Text.Json) JsonSerializer.Serialize A possible object cycle was detected. Objekt odkazuje sám na sebe
.NET number values such as positive and negative infinity cannot be written as valid JSON. NaN a Infinity
PHP 8.4 json_encode() vrátí false, text dá json_last_error_msg() Recursion detected Objekt odkazuje sám na sebe
Inf and NaN cannot be JSON encoded NaN a Infinity
Malformed UTF-8 characters, possibly incorrectly encoded Text v jiném kódování než UTF-8 (PHP)

„Projde bez chyby“ u NaN neznamená správný výsledek: JSON.stringify zapíše null a Python zapíše NaN, které jiné parsery odmítnou. Typ, který Python nezná, hlásí vždy stejně, jen se mění jeho název (Decimal, set, bytes). PHP json_encode výjimku nevyhodí, vrátí false; když výsledek nekontroluješ, odešleš prázdnou odpověď. Použij JSON_THROW_ON_ERROR.

Objekt odkazuje sám na sebe

JSON je strom a odkazy nezná. Autor s polem knih, kde každá kniha odkazuje zpět na autora, se zapsat nedá: převod by nikdy neskončil. Typicky jde o vazby rodič–potomek, entity z ORM s oboustrannými vazbami nebo uzly DOM. Rozhodni, kterou stranu vazby do JSON zapsat.

JavaScript: zpětný odkaz vynechat
const autor = { jmeno: 'Božena Němcová', knihy: [] };
const kniha = { nazev: 'Babička', autor };
autor.knihy.push(kniha);

JSON.stringify(autor);
// TypeError: Converting circular structure to JSON

// Zpětný odkaz při převodu vynech
JSON.stringify(autor, (key, value) => (key === 'autor' ? undefined : value));
// {"jmeno":"Božena Němcová","knihy":[{"nazev":"Babička"}]}
JavaScript: vlastní toJSON vrátí místo objektu jeho název
class Kniha {
  constructor(nazev, autor) {
    this.nazev = nazev;
    this.autor = autor;
  }
  toJSON() {
    return { nazev: this.nazev, autor: this.autor.jmeno };
  }
}
// {"jmeno":"Božena Němcová","knihy":[{"nazev":"Babička","autor":"Božena Němcová"}]}

.NET umí cyklus přerušit volbou ReferenceHandler.IgnoreCycles (zpětný odkaz zapíše jako null) nebo zapsat odkazy přes $id a $ref volbou ReferenceHandler.Preserve. Druhá možnost ale vyžaduje, aby ji znal i příjemce.

C#: ReferenceHandler.IgnoreCycles
var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles };
JsonSerializer.Serialize(kniha, options);
// {"Nazev":"Babi\u010Dka","Self":null}

Typ, který JSON nezná

JSON má jen řetězce, čísla, logické hodnoty, null, objekty a pole. Datum, desetinné číslo s pevnou přesností nebo velké celé číslo se musí převést na jeden z nich a příjemce se musí dozvědět jak, obvykle ze schématu nebo dokumentace API. Datum se nejčastěji zapisuje jako řetězec ISO 8601, velká čísla a částky jako řetězec, aby se nezaokrouhlila.

JavaScript: BigInt jako řetězec
JSON.stringify({ id: 9007199254740993n },
  (key, value) => (typeof value === 'bigint' ? value.toString() : value));
// {"id":"9007199254740993"}
Python: default pro datum, Decimal a set
import json, datetime, decimal

def prevod(o):
    if isinstance(o, (datetime.date, datetime.datetime)):
        return o.isoformat()
    if isinstance(o, decimal.Decimal):
        return str(o)
    if isinstance(o, set):
        return sorted(o)
    raise TypeError(f'Object of type {type(o).__name__} is not JSON serializable')

data = {'vydano': datetime.date(1855, 1, 1), 'cena': decimal.Decimal('249.90'), 'stitky': {'klasika'}}
json.dumps(data, default=prevod, ensure_ascii=False)
# {"vydano": "1855-01-01", "cena": "249.90", "stitky": ["klasika"]}

JSON.stringify převede Date sám (na řetězec ISO 8601 v UTC), ale Map a Set zapíše jako prázdný objekt {} a undefined nebo funkce vynechá, a to bez chyby. Proč na přesnosti velkých čísel záleží, popisuje kapitola Čísla v JSON.

NaN a Infinity

Stejná hodnota, tři výsledky
JSON.stringify({ a: NaN, b: Infinity });
// {"a":null,"b":null}  žádná chyba, hodnota se ztratí

json.dumps({'a': float('nan')})
# {"a": NaN}  to už není JSON, jiné parsery ho odmítnou

json.dumps({'a': float('nan')}, allow_nan=False)
# ValueError: Out of range float values are not JSON compliant: nan

Výsledek výpočtu, který skončil dělením nulou, je potřeba ošetřit před převodem: nahradit null s jasným významem, nebo převod odmítnout. V Pythonu zapni allow_nan=False, ať se neplatný JSON nedostane ven. Co s takovým souborem udělají parsery, ukazuje stránka Neplatné číslo v JSON.

PHP: Malformed UTF-8 characters

json_encode pracuje jen s UTF-8. Text ve Windows-1250 nebo ISO-8859-2 (starší databáze, CSV z Excelu, exporty účetních programů) skončí chybou JSON_ERROR_UTF8. Převeď ho do UTF-8 u zdroje, nebo před převodem na JSON.

PHP: převod z Windows-1250 a výjimka místo false
$nazev = "Babi\xe8ka"; // "Babička" ve Windows-1250, třeba z databáze nebo CSV

json_encode(['nazev' => $nazev]);
// false, json_last_error_msg(): Malformed UTF-8 characters, possibly incorrectly encoded

$json = json_encode(
    ['nazev' => iconv('Windows-1250', 'UTF-8', $nazev)],
    JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE,
);
// {"nazev":"Babička"}

Pozor: mb_convert_encoding v PHP 8.4 kódování Windows-1250 nezná (zná ISO-8859-2) a vyhodí ValueError; pro Windows-1250 použij iconv. Příznak JSON_INVALID_UTF8_SUBSTITUTE chybu potlačí, ale písmena nahradí znakem U+FFFD a data se ztratí. Česká kódování a jejich rozpoznání popisuje kapitola Čeština a kódování v XML; pro JSON platí totéž.