JSON-Serialisierung mit JSR 353/374 und JSR 367

Im Internet hat JSON das XML-Format zwecks Objektübertragung zwischen Server und Browser fast vollständig verdrängt. Das liegt daran, dass ein Browser JSON-Strings direkt in JavaScript-Objekte konvertieren kann, XML-Dokumente aber erst aufwändiger verarbeitet werden müssen.

Neben dem Einsatzgebiet im Internet bietet JSON auch ein kompaktes Format, um etwa lokale Konfigurationsdateien zu kodieren.

JSON im Kontext von JavaScript

Nehmen wir folgende Zeile JavaScript-Code, die ein Person-Objekt mit zwei Properties für Name und Alter definiert. Eine Property wird über ein Schlüssel-Wert-Paar beschrieben:

var person = { „name“ : „Michael Jackson“, „age“ : 50 };

Die Definition eines Objekts geschieht in der JSON (JavaScript Object Notation). Als Datentypen unterstützt JSON Zahlen, Wahrheitswerte, Strings, Arrays, null und Objekte – wie unser Beispiel zeigt. Die Deklarationen können geschachtelt sein, um Unterobjekte aufzubauen.

Zum Zugriff auf die JSON-Daten kommt der Punkt zum Einsatz, sodass der Name nach der Auswertung durch person.name zugänglich ist.

Eine Personenbeschreibung wie diese kann in einem String stehen, der von JavaScript zur Laufzeit ausgewertet wird.

var json = ‚person = { „name“ : „Michael Jackson“, „age“ : 50 };‘;

Der Zugriff auf person.name liefert wie vorher den Namen, denn nach der Auswertung mit eval(…) wird JavaScript ein neues Objekt mit person im Kontext anlegen.

JSON ist besonders praktisch, wenn es darum geht, Daten zwischen einem Server und einem Browser mit JavaScript-Interpreter auszutauschen. Denn wenn der String json nicht von Hand mit einem String initialisiert wurde, sondern ein Server die Zeichenkette person = { … }; liefert, haben wir das, was heutzutage in modernen Ajax-Webanwendungen passiert.

Die letzte Frage ist nun, wie elegant der Server Zeichenketten im Datenaustauschformat JSON erzeugt und so Objekte überträgt. Den String per Hand aufzubauen ist eine Lösung, aber es geht besser.

JSON-Verarbeitung mit der Java API for JSON Processing

Zum Verarbeiten von JSON-Dokumenten gibt es in der Java SE keine Standardklassen, sodass sich eine Reihe von Open-Source-Bibliotheken herausgeprägt haben; Jackson (http://tutego.de/go/jackson) gehört zu den populärsten Lösungen. 2013 wurde dann die JSR 353, »Java API for JSON Processing«, verabschiedet, die Teil der Jakarta EE 7 ist, 2014 dann das JSR 374 »Java API for JSON Processing 1.1«.

Wir können die JSON-API in unseren Java SE-Programmen nutzen, müssen dafür aber Java-Archive im Klassenpfad einbinden. Die Referenzimplementierung befindet sich unter https://javaee.github.io/jsonp/. Am einfachsten haben es Maven-Nutzer, sie binden in ihre POM die Abhängigkeiten auf den „Default provider for JSR 374:Java API for Processing JSON“ ein:

<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.1</version>
</dependency>

Die Implementierung hat eine Abhängigkeit zur JSON-API javax.json:javax.json-api, die wir dann nicht unbedingt selbst in die POM mit aufnehmen müssen.

Aufbau von JSON-Objekten, Formatieren und Parsen

Der Typ JsonObject ist in der API zentral, denn er definiert ein hierarchisches Model mit den Schlüssel-Wert-Paaren eines JSON-Objekts. Um ein JsonObject aufzubauen, können wir über den JsonObjectBuilder gehen, oder wir lassen den Parser JsonReader aus einer String-Repräsentation ein JsonObject erzeugen. Zum formatierten Schreiben in einen Ausgabestrom können wir einen einfachen JsonWriter von der Json-Klasse holen – es geht aber noch einfacher über toString() – oder über die JsonWriterFactory arbeiten, falls wir eine hübsche eingerückte Ausgabe wünschen. Ein Beispiel:

Point p = new Point( 10, 20 );

JsonObjectBuilder objBuilder = Json.createObjectBuilder()
.add( „x“, p.x )
.add( „y“, p.y );
JsonObject jsonObj = objBuilder.build();

System.out.println( jsonObj );  // {„x“:10,“y“:20}

Json.createWriter( System.out ).write( jsonObj ); // {„x“:10,“y“:20}

Map<String, Boolean> config = new HashMap<>();
config.put( JsonGenerator.PRETTY_PRINTING, true );
JsonWriterFactory writerFactory = Json.createWriterFactory( config );

StringWriter out = new StringWriter();
writerFactory.createWriter( out ).write( jsonObj );
System.out.println( out );  // {\n    „x“: 10, …

JsonReader reader = Json.createReader( new StringReader( out.toString() ) );
System.out.println( reader.readObject().getInt( „x“ ) ); // 10

Soll der assoziierte Wert ein Array sein, so wird das Array mit Json.createArrayBuilder().add(..).add(..) aufgebaut und gefüllt.

JSON-Streaming API

So wie es für XML eine Pull-API gibt, existiert sie auch für JSON-Dokumente; das ist von Vorteil, wenn die Daten sehr umfangreich sind. Ein Beispiel zeigt das sehr gut:

URL url = new URL( „https://data.cityofnewyork.us/api/views/25th-nujf/rows.json?accessType=DOWNLOAD“ );

try ( JsonParser parser = Json.createParser( url.openStream() ) ) {
while ( parser.hasNext() ) {
switch ( parser.next() ) {
case KEY_NAME:
case VALUE_STRING:
System.out.println( parser.getString() );
break;
case VALUE_NUMBER:
System.out.println( parser.getBigDecimal() );
break;
case VALUE_TRUE:
System.out.println( true );
break;
case VALUE_FALSE:
System.out.println( false );
break;
case VALUE_NULL:
case START_ARRAY:
case END_ARRAY:
case START_OBJECT:
case END_OBJECT:
// Ignore
}
}
}

Zum Schreiben gibt es Vergleichbares, einen JsonGenerator; die API ist selbsterklärend:

JsonGenerator gen = Json.createGenerator( System.out );
gen.writeStartArray();
gen.writeStartObject();
gen.write( „x“, „12“ );
gen.write( „y“, „2“ );
gen.writeEnd();
gen.writeStartObject()
.write( „x“, „99“ )
.write( „x“, „123“ )
.writeEnd();
gen.writeEnd().close();
// [{„x“:“12″,“y“:“2″},{„x“:“99″,“x“:“123″}]

Der JsonGenerator ist AutoCloseable, sodass er gut in einem try-mit-Ressoucen-Block eingesetzt werden kann.

Objekt-JSON-Mapping

Das JSR 353 beschreibt kein automatisches Objekt-JSON-Mapping, wie JAXB das Objekt-XML-Mapping ermöglicht. Das wird im Standard JSR 367, »Java API for JSON Binding (JSON-B)« definiert. Die Website http://json-b.net/ liefert Hintergrundinformationen, die Referenzimplementierung ist Eclipse Yasson.

Um das mit einem Beispiel testen zu können, nehmen wie Yasson in unsere POM mit auf:

<dependency>

<groupId>org.eclipse</groupId>

<artifactId>yasson</artifactId>

<version>1.0.1</version>

</dependency>

Die Dependency selbst hat eine Maven Abhängigkeit auf javax.json.bind:javax.json.bind-api, also der eigentliche API.

Zur Abbildung bauen wir uns ein Beispielobjekt und bringen es in das JSON-Format. Im zweiten Schritt übertragen wir den JSON-String wieder auf das Objekt:

public class JsonbDemo {

public static class EvilLaboratory {

public String from;

public double volume;

public boolean didPayElectricityBill;

public List<String> items;

}

public static void main( String[] args ) {

EvilLaboratory lab1 = new EvilLaboratory();

lab1.from = „Frank“;

lab1.didPayElectricityBill = true;

lab1.volume = 12442.33;

lab1.items = List.of( „corpses“, „animals“ );

 

Jsonb jsonbuilder = JsonbBuilder.create();

String jsonString = jsonbuilder.toJson( lab1 );

System.out.println( jsonString );

 

EvilLaboratory lab2 = jsonbuilder.fromJson( jsonString, EvilLaboratory.class );

System.out.printf( „from=%s, volume=%s, didPayElectricityBill=%s, items=%s“,

lab2.from, lab2.volume, lab2.didPayElectricityBill, lab2.items );

}

}

Über Christian Ullenboom

Ich bin Christian Ullenboom und Autor der Bücher ›Java ist auch eine Insel. Einführung, Ausbildung, Praxis‹ und ›Java SE 8 Standard-Bibliothek. Das Handbuch für Java-Entwickler‹. Seit 1997 berate ich Unternehmen im Einsatz von Java. Sun ernannte mich 2005 zum ›Java-Champion‹.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.