deutsche version  English version
Ein rekursiver Java-Parser für JSON-Strings
Software

JSON-Parser

JSON (JavaScript Object Notation) ist ein einfaches Format zum Datenaustausch. Ein JSON-Objekt ist im Prinzip eine Liste von Paaren aus Namen und Werten, die durch Kommas getrennt und von geschweiften Klammern umgeben sind; z. B.:

{
    "name" : "Mike",
    "age" : 23,
    "hobbies" : [ "fishing", "rugby", "computer games" ]
}

Die Werte können verschiedene Arten von Daten enthalten, z. B. Zeichenketten, Zahlen oder sogar Felder. Das erscheint zunächst ziemlich einfach, kann aber schnell kompliziert werden, wenn ein Wert ein anderes JSON-Objekt ist. Dann liegt nämlich eine rekursive Struktur vor, und ein JSON-Objekt wird am besten durch einen Baum aus einzelnen Knoten repräsentiert. Alle Zeichenketten (auch die Namen der Werte) müssen in Anführungszeichen eingeschlossen, alle anderen Datentypen dürfen das nicht sein. Einzelheiten finden Sie bei www.json.org.

Eigenschaften

Der JSON-Parser ist eine Java-Bibliothek namens JsonFse in einer JAR-Datei. Er ist eine einfache Implementierung, die ausschließlich Java-Strings als Eingabe akzeptiert. Ich habe den Parser für mein Camera RC-Projekt gebraucht und hatte keine Lust, mich mit anderen, komplizierteren JSON-Bibliotheken auseinanderzusetzen. Und außerdem versprach es Spaß, nach vielen Jahren mal wieder einen rekursiven Parser zu schreiben!
  • Der Parser analysiert den Eingabe-String, d. h. erzeugt den Parse-Baum und stellt jedes erkannte Blatt in den Baum.
  • Der Parser entdeckt syntaktische Fehler im String und zeigt ihre Position.
  • Man kann sich durch den Baum bewegen und einzelne Elemente erhalten.
  • Man kann einen Baum auch durch Hinzufügen einzelner Elemente erzeugen oder modifizieren.
  • Es gibt eine ausführliche Dokumentation im Java-Doc-Format.
  • Die Bibliothek ist Open Source und steht unter der LGPL.
Für eigene Verbesserungen kann man das komplette Netbeans-Quelltext-Projekt herunterladen, das auch JUnit-Testroutinen enthält.

Beispiele

Um einen JSON-String zu parsen und den Baum aufzubauen, muß er als Java String an den Konstruktor der JsonFse.JsonObj-Klasse übergeben werden. Beispiel:

import JsonFse.*;
String s = "{" +
               "\"name\" : \"Mike\"," +
               "\"age\"  : 23,"       +
               "\"hobbies\" : [ \"fishing\", \"rugby\", \"computer games\" ]" +
           "}";
try
{
   JsonObj jo = new JsonObj(s);
   System.out.println(jo.getAsString());
}
catch (ParseErrorEx ex)
{
   System.out.println(ex.getErrMsg());
}

JsonObj.getAsString() rückübersetzt den Baum dann rekursiv wieder in einen formatierten String. Wenn der Original-String auf Grund von Fehlern in der Struktur nicht geparsed werden konnte, wird eine ParseErrorEx-Ausnahme geworfen. ParseErrorEx.getErrPos() liefert den Index im String, bei dem der Fehler erkannt wurde, ParseErrorEx.getErrMsg() gibt einen ganzen Fehler-String aus.

Damit man den Baum durchlaufen kann, braucht man das Wissen über die Knotentypen. Die Typnamen orientieren sich an den Bezeichnungen aus www.json.org. Die abstrakte Basisklasse ist Node, NodePair repräsentiert ein Paar, NodeNumber eine Zahl usw. Um Paare aus dem Baum zu erhalten, gibt es die Methoden JsonObj.getNext() and JsonObj.get(). Die gleichen Methoden gibt es auch in der NodeArray-Klasse. Das folgende Beispiel durchläuft das JSON-Objekt jo, das oben erzeugt wurde:

NodeString ns, nsa;
NodeValue  nv;
NodePair   np = jo.getNext();
while (np != null)
{
   ns = np.getString();
   System.out.println("label: " + ns.getAsString());
   nv = np.getValue();
   if (nv instanceof NodeArray)
   {
      nsa = (NodeString)nv.get(1);
      System.out.println("Second hobby of Mike: " + nsa.getAsString());
   }
   np = jo.getNext();
}
System.out.println("First pair in jo: " + jo.get(0).getAsString());

Das erzeugt die folgende Ausgabe:
label: "name"
label: "age"
label: "hobbies"
Second hobby of Mike: "rugby"
First pair in jo: "name" : "Mike"

Wie man sieht, ist es einfach, einen speziellen Wert aus einem JSON-Objekt zu extrahieren, wenn man die Struktur des Baumes schon kennt. Wenn das nicht der Fall ist, muß man sie erkennen, während man den Baum durchläuft. Dazu benutzt man das Java-Kommando instanceof wie oben gezeigt.

Weitere Einzelheiten dazu und zur Modifikation von Bäumen finden Sie in der Dokumentation der Bibliothek.

Grammatik

Die Implementierung parsed JSON-Objekte, die der folgenden Grammatik entsprechen und hier in EBNF (erweiterte Backus-Naur-Form) notiert ist:

object    = '{' , [ members ] , '}' ;
members   = pair , { ',' , pair } ;
pair      = string , ':' , value ;
string    = '"' , { char } , '"' ;
char      = ? any Unicode character ? - ctrlChar - '\') | ctrlChar | hexNumber ;
ctrlChar  = '\"' | '\\' | '\/' | '\b' | '\f' | '\n' | '\r' | '\t' ;
hexNumber = '\u' , hexDigit , hexDigit , hexDigit , hexDigit ;
hexDigit  = digit09 | hexChar ;
digit09   = digit0 | digit19 ;
digit0    = '0' ;
digit19   = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ;
hexChar   = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' ;
value     = string | number | object | array | 'true' | 'false' | 'null' ;
number    = int | int frac | int exp | int , frac , exp ;
int       = [ '-' ] , digit | digit19 , digits ;
frac      = '.' , digits ;
exp       = eE , digits
digits    = digit09 , { digit09 } ;
eE        = 'e' | 'E' [ '+' | '-' ] ;
array     = '[' , [ elements ] , ']' ;
elements  = value , { ',' , value } ;

Zur Erinnerung zeigt die folgende Tabelle die metasprachlichen Symbole der EBNF:

Symbol Bedeutung
= Definition
, Verkettung
; Ende
| Alternative
[ ... ] Option
{ ... } Wiederholung
' ... ' Zeichenkette
? ... ? Spezielle Sequenz
- Ausnahme

Klassendiagramm

Das UML-Klassendiagramm sieht so aus:

UML Class
              Diagram

Download

Datei
Größe Beschreibung Version Plattform
JsonFse_110.zip 591 kB JSON-Parser-Bibliothek
1.1.0 JRE 1.8.0 oder neuer
JsonFse_src_110.zip
97 kB JSON-Parser-Bibliothek, Quelltexte als Netbeans-Projekt 1.1.0 Netbeans IDE 9.0

Änderungsliste

16. Dezember 2018, V.1.1.0:
  • Neuer Konstruktor JsonObj(NodeObj) und neue Methode JsonObj.setNodeObj(NodeObj) implementiert; ein bereits geparstes JSON Objekt kann damit wiederverwendet werden, ohne daß man es erneut aus einem String parsen muß.
  • Neuer Konstruktor NodePair(String, int) und neue Methode JsonObj.addPair(String, int) zur Handhabung von Integer-Werten.
1. Juli 2017, V.1.0.0:
  • Erste veröffentlichte Version.
28. Dezember 2016, V.0.9.0:
  • Neue Methode getAsInt() hinzugefügt.
  • JUnit-Tests hinzugefügt.

Links

  • Das ist die JSON home page mit Links zu diversen Parser-Implementierungen.
  • Die offizielle Java-Implementierung kommt natürlich von Oracle.
  • Und hier noch ein Vergleich zwischen vier anderen populären Java-Implementierungen.