Skip to main content
Developer Tools

Fehlerhaftes JSON debuggen: ein Feldhandbuch

Die Parse-Meldung ist besser als ihr Ruf. Wie man liest, was V8 heute sagt, die fünf Fehlerarten hinter kaputten Payloads, und die zwei Meldungen ohne Position.

Von 8 min read
Titelkarte des Artikels. Ein Paar große geschweifte Klammern mit einem bernsteinfarbenen Warnzeichen dazwischen, mit der Zeile: the parser knows where it broke

Alle zitieren dieselbe Meldung, wenn sie sich über JSON beschweren: SyntaxError: Unexpected token } in JSON at position 4217. Eine Position, keine Erklärung, viel Glück.

Diese Meldung ist ein Museumsstück. Geben Sie dasselbe kaputte Objekt heute an Node 24, und Sie bekommen dies:

SyntaxError: Expected double-quoted property name in JSON at position 51 (line 4 column 1)

Sie benennt, was der Parser erwartet hat, den Offset sowie Zeile und Spalte. Die Meldung ist gut geworden, während das Gerede darüber schlecht geblieben ist, und die meisten Debugging-Tipps, die Sie finden, sind noch für die alte geschrieben.

Es folgt also eine Anleitung zum Lesen der Meldung, die Sie tatsächlich bekommen, zu den fünf Fehlern hinter fast jedem kaputten Payload, und zu der einen Fehlerfamilie, die immer noch nicht verrät, wo es passiert ist.

Vorher eine Sache ausschliessen: dass die Datei überhaupt kein striktes JSON ist. Konfigurationen für VS Code oder den TypeScript-Compiler sind JSONC, und ein Kommentar oder ein nachgestelltes Komma lässt JSON.parse scheitern, während im Editor alles normal aussieht. Der JSON-Dialekt-Detektor klärt das mit einem Einfügen.

Die Erwartung lesen, nicht nur den Offset

Ein vierzeiliges JSON-Objekt mit nachgestelltem Komma am Ende von Zeile 3. Die Meldung nennt Position 51, Zeile 4 Spalte 1, also die schliessende Klammer, während das verursachende Komma eine Zeile darüber steht
Der Parser ziert sich nicht. Ein Komma verspricht einen weiteren Key, und an der Klammer wird das Versprechen fällig.

Die Position ist, wo die Grammatik brach. Der Fehler steht meist ein paar Zeichen weiter links, denn die meisten JSON-Fehler sind ein gegebenes und nicht gehaltenes Versprechen: ein Komma, das ein weiteres Element verspricht, ein öffnendes Anführungszeichen, das ein schliessendes verspricht.

"Expected double-quoted property name" heisst, dass etwas einen Property-Namen versprochen hat. Suchen Sie von der genannten Position nach links, bis Sie finden, was das war.

Die fünf üblichen Ursachen

Ein nachgestelltes Komma

Mit weitem Abstand die häufigste. Sie haben das letzte Element gelöscht und sein Komma stehen lassen.

{
  "name": "zeroutil",
  "category": "developer",
}

JavaScript akzeptiert das. JSON5 akzeptiert das. Striktes JSON nicht.

Finden lassen sie sich mit ,\s*[}\]] in unserem Regex-Tester . Verhindern lassen sie sich mit einem Formatter als Pre-Commit-Hook, denn JSON.stringify erzeugt nie eines. Ein nachgestelltes Komma ist immer ein Artefakt menschlicher Bearbeitung.

Keys ohne oder mit einfachen Anführungszeichen

{
  name: "zeroutil",
  'category': "developer"
}

Keine der beiden Formen ist JSON, und genau das entsteht, wenn jemand JSON aus dem Gedächtnis tippt. Die Meldung ist hier ungewöhnlich klar: Expected property name or '}' in JSON at position 4 (line 2 column 3), also das n von name. In einer grossen Datei listet grep -nE '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*:' die betroffenen Zeilen.

Nicht maskierte Zeichen in Strings

Drei Dinge müssen in einem JSON-String maskiert werden: das doppelte Anführungszeichen, der Backslash und jedes Steuerzeichen von U+0000 bis U+001F1. Die letzte Gruppe ist die gemeine, denn ein echter Zeilenumbruch in einem String ist ein Steuerzeichen.

{
  "bio": "line one
line two"
}

Sie bekommen Bad control character in string literal in JSON at position 20 (line 2 column 19), und Position 20 ist der Zeilenumbruch selbst, nicht das Zeichen danach. Der alte Rat, in der Zeile darüber nachzusehen, ist für diesen Fall nicht mehr nötig.

Die eigentliche Lehre liegt weiter oben. Bauen Sie JSON mit JSON.stringify oder dem Äquivalent Ihrer Sprache, nie durch Zusammenkleben von Strings in einem Template, denn genau so kommt ein nicht maskiertes Anführungszeichen überhaupt erst in einen Payload.

NaN, Infinity, undefined

JSON.stringify macht aus NaN und Infinity ein null. undefined wird als Objekteigenschaft weggelassen und als Array-Element zu null2, ein Unterschied, den man kennen sollte, bevor man nach einem fehlenden Array-Platz sucht.

Ein Payload mit literalem NaN kam also nicht aus JavaScript. Sehr oft kam er aus Python, dessen json-Modul standardmässig in beide Richtungen nicht strikt ist: dumps gibt NaN, Infinity und -Infinity aus, und loads akzeptiert sie beim Einlesen wieder3. Die Dokumentation sagt selbst, dass die Ergebnisse kein gültiges JSON sind. Genau diese Symmetrie sorgt dafür, dass es niemandem auffällt: Der Payload läuft innerhalb von Python sauber hin und zurück und fliegt in dem Moment auseinander, in dem ein Go- oder Java-Dienst ihn liest.

Reparieren Sie es beim Erzeuger mit json.dumps(obj, allow_nan=False), was schon beim Serialisieren einen Fehler wirft, statt etwas Unparsbares auszuliefern.

Ein Byte Order Mark

Manche Editoren und Exporte stellen U+FEFF voran. RFC 8259 verbietet, eines an übertragenes JSON anzuhängen, erlaubt einem Parser aber ausdrücklich, eines zu ignorieren statt zu scheitern1. Beide Verhalten sind konform, und Sie können sich nicht herleiten, welches Sie bekommen.

In der Praxis lehnen es sowohl JSON.parse als auch Pythons json.loads ab. Pythons Meldung ist die hilfreichere von beiden: Unexpected UTF-8 BOM (decode using utf-8-sig).

$ file config.json
config.json: Unicode text, UTF-8 (with BOM) text

$ head -c 3 config.json | xxd -p
efbbbf

Entfernen lässt es sich mit sed '1s/^\xEF\xBB\xBF//' config.json oder indem Sie im Editor als "UTF-8 ohne BOM" speichern.

Die Fehler, die nicht verraten, wo sie sind

Jetzt der Teil, der Zeit kostet, und der Grund, warum jeder von Stack Overflow kopierte Positions-Kratzer irgendwann bricht.

V8 erzeugt zwei Formen von Meldung, und nur eine enthält eine Position.

Sechs fehlerhafte JSON-Eingaben mit dem jeweils erzeugten Fehler, gruppiert in eine Familie mit Position, Zeile und Spalte und eine zweite Familie, die den Payload ohne Position zurückzitiert
Gemessen mit Node 24.18. Bei der zweiten Familie liefert die naive Regex NaN.

Die zweite Familie zitiert stattdessen einen Ausschnitt des Payloads zurück. Bei einem kurzen Payload ist das unproblematisch, weil der Ausschnitt das Ganze ist. Bei einem grossen ist es ein Fragment mit Auslassungspunkten, und das reicht immer noch, denn danach können Sie suchen.

function locate(text) {
  try {
    JSON.parse(text);
    return null;
  } catch (err) {
    const pos = err.message.match(/position (\d+)/);
    if (pos) return Number(pos[1]);

    const quoted = err.message.match(/"(.+)" is not valid JSON$/s);
    if (quoted) {
      const at = text.indexOf(quoted[1].replace(/^\.\.\./, ''));
      if (at >= 0) return at;
    }
    return null;
  }
}

Das deckt beide Familien ab. Gegen einen Payload mit einem NaN-Literal an Position 107 getestet findet der Positions-Zweig nichts, und der Ausschnitt-Zweig landet auf 107.

Wenn Sie einen Offset haben, lassen Sie die Umgebung ausgeben, statt Zeichen von Hand zu zählen:

const at = locate(payload);
console.log(payload.slice(Math.max(0, at - 80), at + 80));

Eine frühere Fassung dieses Artikels empfahl stattdessen eine Bisektion: den Payload halbieren und jedes Präfix mit angeklebtem '}]'.repeat(10) erneut parsen. Das funktioniert nicht, und ich hätte es vor dem Veröffentlichen ausführen sollen. Fast kein Präfix plus zehn schliessende Klammern ist gültiges JSON, die Suche fällt also schon im ersten Schritt auf das erste Byte zusammen. Bei einem 4,6 KB grossen Testpayload mit einem echten Syntaxfehler an Index 4593 lieferte sie 1.

Bisektion ist ohnehin der falsche Reflex. Der Parser kennt den Offset bereits und gibt ihn heraus.

Wenn Sie lieber kein JavaScript schreiben

jq meldet die Position in der Form, mit der die meisten am besten arbeiten:

$ jq . config.json
jq: parse error: Expected another key-value pair at line 3, column 1

Zeile und Spalte schlagen einen Zeichen-Offset für alles, was Sie danach im Editor öffnen. Unser JSON-Formatter erledigt dasselbe im Browser-Tab und zeigt auf das Zeichen.

Gegen die Version diffen, die funktioniert hat

Wenn ein Payload gestern noch geparst wurde und heute scheitert, ist der Vergleich schneller als das Lesen von beidem. Schicken Sie beide Fassungen zuerst durch den Formatter, damit strukturelle Änderungen als Zeilenänderungen auftauchen und sich nicht in einer riesigen Zeile verstecken, und geben Sie sie dann in den Diff-Checker , der vollständig im Browser läuft. Letzteres zählt, wenn der Payload eine Produktionsantwort ist, die Sie nicht auf den Server eines Fremden kopieren sollten.

Auf Abbruch prüfen, bevor Sie auf Syntax prüfen

Kommt der Payload aus einer API und ist das letzte Zeichen weder } noch ], dann ist das kein Syntaxfehler. Der Body wurde abgeschnitten: eine Verbindung geschlossen, ein Puffer zu früh geleert, ein Timeout mitten in der Serialisierung. Der Text hört einfach auf, oft mitten in einem String, und Sie bekommen Unterminated string in JSON at position ....

Keine Parser-Option repariert das. Der Fehler liegt beim Erzeuger.

Fünf Minuten, kaputter Payload, los

  1. Die Erwartung in der Meldung lesen, dann von der Position aus nach links schauen.
  2. Das letzte Zeichen prüfen. Weder } noch ] heisst Abbruch, und hier sind Sie fertig.
  3. Nach ,\s*[}\]] greppen. Nachgestellte Kommas bleiben Nummer eins.
  4. Die ersten drei Bytes auf ef bb bf prüfen.
  5. Nach NaN, Infinity und undefined greppen. Wenn Sie fündig werden, reden Sie mit dem, der die Datei erzeugt hat.

Das meiste kaputte JSON ist einer dieser Fälle. Wirklich seltsame Fehler, tiefe strukturelle Schäden oder gemischte Kodierungen, sind selten genug, um die volle halbe Stunde zu verdienen, wenn sie auftauchen.

Eine Sache, die man nicht tun sollte

Tools, die fehlerhaftes JSON "reparieren", indem sie es grosszügig umdeuten, raten, was Sie gemeint haben, und verdecken den Fehler beim Erzeuger, der Ihnen nächste Woche einen schlimmeren Payload schickt. Reparieren Sie die Quelle. Der Parser hatte recht.

Quellen

Jede Zahl in diesem Artikel lässt sich auf eine Quelle unten zurückführen. Behauptungen ohne Beleg wurden gestrichen, nicht abgeschwächt.

  1. PrimärquelleIETF

    Abschnitt 7, dass Anführungszeichen, Backslash und die Steuerzeichen U+0000 bis U+001F innerhalb von Strings maskiert werden müssen, und Abschnitt 8.1, dass ein Parser ein Byte Order Mark ignorieren darf, statt es als Fehler zu behandeln.

  2. PrimärquelleMDN Web Docs

    Dass Infinity und NaN als null serialisiert werden und dass undefined in einem Objekt weggelassen wird, in einem Array dagegen zu null wird.

  3. PrimärquellePython Software Foundation

    Dass allow_nan auf True steht und damit NaN und Infinity ausgegeben werden, dass der Decoder diese Literale beim Einlesen akzeptiert, und die Aussage der Dokumentation selbst, dass die Ergebnisse kein gültiges JSON sind.

Themen

  • JSON
  • Debugging
  • Parsers
  • Regex
  • Diff

In diesem Artikel erwähnte Tools

  • JSON Dialect Detector - Tell whether a file is JSON, JSONC or JSON5 - and convert it to strict JSON.
  • JSON Formatter - Format, validate and minify JSON with syntax highlighting.
  • Regex Tester - Test regular expressions with live highlighting, matches and capture groups.
  • Diff Checker - Compare code or text with line-by-line diff and unified output.

Neue Tools per E-Mail

Neue Tools und gelegentlich ein ausführlicher Artikel, etwa einmal im Monat. Kein Spam, deine Adresse bekommt niemand, Abmeldung mit einem Klick.

Ähnliche Artikel

Titelkarte des Artikels. Ein großes Paar bernsteinfarbener geschweifter Klammern, mit der Zeile: one format, three dialects
Developer Tools Guide

Der JSON-Leitfaden für Entwickler: Alles Wichtige für 2026

Die Spezifikation, die uneinigen Dialekte, die Zahlen, die still kaputtgehen, und der Sicherheitsrat, der auf die falsche Rekursion zeigt. Mit Cluster-Karte.

Die Wörter JSON, JSONC und JSON5 übereinander in großer weißer Schrift auf dunklem Grund, eingerahmt von zwei überdimensionierten geschweiften Klammern
Developer Tools

JSON vs JSON5 vs JSONC: Was welcher Parser wirklich akzeptiert

Drei Formate, eine Dateiendung und keine Einigkeit darüber, was gültig ist. Gemessen an den echten Parsern, darunter zwei, die sich widersprechen und dieselbe Bibliothek sind.

Titelkarte des Artikels. Drei graue Quadrate und ein Pfeil führen zu vier schmaleren bernsteinfarbenen Balken, mit der Zeile: 3 bytes become 4 characters
Developer Tools

Base64 erklärt: Was es ist, warum es existiert und wann nicht

Was Base64 mit Ihren Bytes macht, die drei Varianten, die fast alle Decode-Fehler verursachen, und die Stellen, an denen es sich lohnt.