Blog

Blog

Neues aus unserem Blog

Was nicht passt wird passend gemacht

Jochen Just Montag, 22. August 2016 @ 09:19 geschrieben von Jochen Just

Beim Serialisieren von Java-Objekten zu JSON gibt es Fälle, bei denen man als Entwickler keine 1:1 Abbildung des Objekt-Graphen auf die resultierende JSON-Struktur haben möchte.

Stellen Sie sich eine Klasse vor, die viele Daten enthält:

public class KlasseMitVerdammtVielenDaten {

  @JsonProperty
  private String text;

  @JsonProperty
  private Collection vieleVieleDaten;
  // viele weitere Membervariablen ausgelassen
}

Diese Klasse nimmt eine zentrale Stelle Ihrer Anwendung ein und wird an vielen Stellen eingesetzt. Außerdem ist die Logik, die diese Klasse mit Daten füllt, sehr komplex.

Bisher kein Problem. Jacksons ObjectMapper bekommt das spielend leicht hin und somit auch Sie. Ergebnis:

{
  "text": "Testtext",
  "vieleVieleDaten": [
    { .... },
    { .... }
  ]
}

Nun kommt ein neuer Anwendungsfall, der eine JSON-Struktur fordert, die der obigen sehr ähnlich ist. Aber es ist noch eine weitere Information von Nöten und zwar auf der obersten Ebene der JSON-Struktur:

{
  "wahnsinnigWichtigeZusatzInfo": false,
  "text": "Testtext",
  "vieleVieleDaten": [
    { .... },
    { .... }
  ]
}

Ansatz mittels Vererbung

Im Prinzip kein Problem. Man erstellt ein neue Klasse, die von KlasseMitVerdammtVielenDaten erbt und spendiert noch ein passendes Field.

public class KlasseMitVerdammtVielenDatenUndWichtigerZusatzInfo
   extends KlasseMitVerdammtVielenDaten {

  @JsonProperty
  boolean wahnsinnigWichtigeZusatzInfo;
}

Aber wie erzeugt und füllt man nun Objekte dieser neuen Klasse?

Man könnte die Logik, die die Datenstruktur befüllt, so anpassen, dass sie mit beiden Klassen der Vererbungshierarchie umgehen kann.

Sah das Interface zum Befüllen ursprünglich so aus:

KlasseMitVerdammtVielenDaten irrsinnigKomplexeBefüllung();

muss die neue Logik irgendwoher wissen, welches Objekt denn zu erzeugen ist.

Z.B. man erzeugt das Objekt selbst und die Logik füllt nur noch die Datenstruktur

void irrsinigKomplexeBefüllung(final KlasseMitVerdammtVielenDaten objektZumFüllen);

Mit Java 8 könnte man als Parameter einen Supplier übegeben, der bei Bedarf das Objekt des richtigen Typs erzeugt.

<T extends Klassemitverdammtvielendaten> T irrsinigKomplexeBefüllung
  (final Supplier<T> supplier);

Beide Ansätze haben das Problem, dass man den Parameter evtl. durch viele Klassen oder Interfaces hindurchschleifen muss, hinter denen sich die tatsächliche Erzeugung des Objekts versteckt.

Um dies zu vermeiden, könnte man der Klasse einen Copy-Constructor spendieren und danach das neue Field mit Werten füllen.

public KlasseMitVerdammtVielenDatenUndWichtigerZusatzInfo
   (KlasseMitVerdammtVielenDaten copyFrom) {
  // hier "einfach" alles kopieren
}
  
...
KlasseMitVerdammtVielenDaten kmvdvd = füllLogik.irrsinigKomplexeBefüllung();
KlasseMitVerdammtVielenDatenUndWichtigerZusatzInfo kmvduwzi = 
  new KlasseMitVerdammtVielenDatenUndWichtigerZusatzInfo(kmvdvd);
kmvduwzi.wahnsinnigWichtigeZusatzInfo = false;

Dieser Ansatz hat den Nachteil, dass man im Copy-Constructor in Zukunft nie vergessen darf, neue Fields ebenfalls zu kopieren.

Wie man sieht, finden sich Lösungen. Aber besonders elegant scheinen sie mir alle nicht.

Komposition alleine funktioniert nicht

Die klassische Alternative zur Vererbung ist die Komposition.

Modelliert man die neue Java-Datenstruktur entsprechend, funktioniert dies (nicht ganz unerwartet) nicht.

public class KlasseMitVerdammtVielenDatenUndWichtigerZusatzInfo {

  @JsonProperty
  boolean wahnsinnigWichtigeZusatzInfo;

  @JsonProperty
  KlasseMitVerdammtVielenDaten basisDaten;
}

Das Ergebnis daraus stellt sich als JSON wie folgt dar:

{
  "wahnsinnigWichtigeZusatzInfo": false,
  "basisDaten": {
    "text": "Testtext",
    "vieleVieleDaten": [
      { .... },
      { .... }
    ]
  }
}

Einfache Lösung mit Jackson

Dank der Annotation @JsonUnwrapped lässt sich das Ganze mit Jackson elegant erledigen:

public class KlasseMitVerdammtVielenDatenUndWichtigerZusatzInfo {

  @JsonProperty
  int wahnsinnigWichtigeZusatzInfo;

  @JsonUnwrapped
  @JsonProperty
  KlasseMitVerdammtVielenDaten basisDaten;
}

Man bildet das Java-Modell über Komposition ab, aber dank @JsonUnwrapped kann man die JSON-Struktur unabhängig davon beeinflussen. @JsonUnwrapped führt quasi wahnsinnigWichtigeZusatzInfo und basisDaten zusammen. Am Ende sieht die JSON-Struktur wie oben beschrieben aus und man kann die Logik zum Befüllen der Datenstruktur ohne Änderungen wiederverwenden.

...
final KlasseMitVerdammtVielenDaten kmvd = irrsinnigKomplexeBefüllung ();
final KlasseMitVerdammtVielenDatenUndWichtigerZusatzInfo kmvduwzi =
   new KlasseMitVerdammtVielenDatenUndWichtigerZusatzInfo ();
kmvduwzi.wahnsinnigWichtigeZusatzInfo = false;
kmvduwzi.basisDaten = kmvd;

Nachteil dieser Lösung ist, dass man ein Objekt mehr erzeugt als mit anderen Lösungen. Meiner Meinung nach ein guter Tausch.

Zum Schluss bleibt noch die spannende Frage, wie sich @JsonUnwrapped bei Fields mit gleichem Namen auswirkt.

Ausgangsposition:

public class Daten {

  @JsonProperty
  private Integer nummer;
}

public class UnwrappedDaten {

  @JsonUnwrapped
  private Daten daten;

  @JsonProperty
  private Integer nummer;
}

Serialisiert man obige Objektstruktur, sieht das JSON wie folgt aus:

{
  "nummer":2,
  "string":"text",
  "nummer":1
}

Das Feld nummer kommt jeweils mit dem richtigen Wert vor. Deserialisiert man diese JSON-Strukur wieder in obige Objekt-Struktur ein, ist das Field UnwrappedDaten.nummer 1 und das Field Daten.nummer null.

Daher sollte man beim Einsatz von @JsonUnwrapped darauf achten, entweder Namen nicht mehrmals zu verwenden oder eine der beiden Properties prefix oder suffix der Annotation zu verwenden. Auf diese Weise vermeidet man einfach Namenskollisionen.


zur Übersicht

Willkommen auf dem avono Blog

Hier auf dem avono Blog finden Sie in regelmäßigen Abständen sowohl technische Neuigkeiten aus unserer Partnerproduktwelt als auch nützliche Entwicklertipps.
Und jetzt kommt der obligatorische Disclaimer: Die Ausführungen der Blogeinträge spiegeln nicht die Meinung der avono AG sondern nur die Sicht der einzelnen Autoren wider.

Weitere Blogeinträge