Felder von der Serialisierung in Gson ausschließen

Felder von der Serialisierung in Gson ausschließen

1. Überblick

In diesem kurzen Tutorial werden die verfügbaren Optionen zum Ausschließen eines oder mehrerer Felder einer Java-Klasse und ihrer Unterklassen von der Gson-Serialisierung untersucht.

2. Ersteinrichtung

Definieren wir zunächst unsere Klassen:

@Data
@AllArgsConstructor
public class MyClass {
    private long id;
    private String name;
    private String other;
    private MySubClass subclass;
}

@Data
@AllArgsConstructor
public class MySubClass {
    private long id;
    private String description;
    private String otherVerboseInfo;
}

Wir haben sie der Einfachheit halber mitLombok versehen (syntaktischer Zucker für Getter, Setter, Konstruktoren ...).

Füllen wir sie jetzt aus:

MySubClass subclass = new MySubClass(42L, "the answer", "Verbose field not to serialize")
MyClass source = new MyClass(1L, "foo", "bar", subclass);

Unser Ziel ist es zu verhindern, dass die FelderMyClass.other undMySubClass.otherVerboseInfo Serialisiert werden.

Die erwartete Ausgabe ist:

{
  "id":1,
  "name":"foo",
  "subclass":{
    "id":42,
    "description":"the answer"
  }
}

In Java:

String expectedResult = "{\"id\":1,\"name\":\"foo\",\"subclass\":{\"id\":42,\"description\":\"the answer\"}}";

3. Transient Modifier

Wir können ein Feld mit dem Modifikatortransient markieren:

public class MyClass {
    private long id;
    private String name;
    private transient String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    private transient String otherVerboseInfo;
}

Gson Serializer ignoriert jedes als transient deklarierte Feld:

String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString);

Dies ist zwar sehr schnell, bringt aber auch einen schwerwiegenden Nachteil mit sich:every serialization tool will take transient into account, nicht nur Gson.

Transient ist die Java-Methode, um von der Serialisierung auszuschließen. Dann wird unser Feld auch nach der Serialisierung vonSerializableund nach jedem Bibliothekstool oder Framework, das unsere Objekte verwaltet, herausgefiltert.

Darüber hinaus funktioniert das Schlüsselworttransientimmer sowohl für die Serialisierung als auch für die Deserialisierung, was je nach Anwendungsfall einschränkend sein kann.

4. @Expose Anmerkung

Die Annotation von Gsoncom.google.gson.annotations @Exposefunktioniert umgekehrt.

Wir können damit deklarieren, welche Felder serialisiert werden sollen, und die anderen ignorieren:

public class MyClass {
    @Expose
    private long id;
    @Expose
    private String name;
    private String other;
    @Expose
    private MySubClass subclass;
}

public class MySubClass {
    @Expose
    private long id;
    @Expose
    private String description;
    private String otherVerboseInfo;
}

Dazu müssen wir Gson mit einem GsonBuilder instanziieren:

Gson gson = new GsonBuilder()
  .excludeFieldsWithoutExposeAnnotation()
  .create();
String jsonString = gson.toJson(source);
assertEquals(expectedResult, jsonString);

Dieses Mal können wir auf Feldebene steuern, ob die Filterung für Serialisierung, Deserialisierung oder beides erfolgen soll (Standard).

Lassen Sie uns sehen, wie Sie verhindern, dassMyClass.other serialisiert werden, aber dass es während einer Deserialisierung von JSON gefüllt wird:

@Expose(serialize = false, deserialize = true)
private String other;

Dies ist zwar der einfachste von Gson bereitgestellte Weg und wirkt sich nicht auf andere Bibliotheken aus. Dies kann jedoch zu Redundanz im Code führen. Wenn wir eine Klasse mit hundert Feldern haben und nur ein Feld ausschließen möchten, müssen wir neunundneunzig Anmerkungen schreiben, was übertrieben ist.

5. ExclusionStrategy

Eine hochgradig anpassbare Lösung ist die Verwendung voncom.google.gson.*ExclusionStrategy*.

Hiermit können wir (extern oder mit einer anonymen inneren Klasse) eine Strategie definieren, mit der der GsonBuilder angewiesen wird, Felder (und / oder Klassen) mit benutzerdefinierten Kriterien zu serialisieren.

Gson gson = new GsonBuilder()
  .addSerializationExclusionStrategy(strategy)
  .create();
String jsonString = gson.toJson(source);

assertEquals(expectedResult, jsonString);

Sehen wir uns einige Beispiele für intelligente Strategien an.

5.1. Mit Klassen- und Feldnamen

Natürlich können wir auch einen oder mehrere Feld- / Klassennamen fest codieren:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        if (field.getDeclaringClass() == MyClass.class && field.getName().equals("other")) {
            return true;
        }
        if (field.getDeclaringClass() == MySubClass.class && field.getName().equals("otherVerboseInfo")) {
            return true;
        }
        return false;
    }

    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }
};

Dies ist schnell und direkt auf den Punkt gebracht, aber nicht sehr wiederverwendbar und auch fehleranfällig, falls wir unsere Attribute umbenennen.

5.2. Mit Geschäftskriterien

Da wir einfach einen Booleschen Wert zurückgeben müssen, können wir jede beliebige Geschäftslogik in diese Methode implementieren.

Im folgenden Beispiel identifizieren wir jedes Feld, das mit "Sonstige" beginnt, als Felder, die nicht serialisiert werden sollten, unabhängig von der Klasse, zu der sie gehören:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getName().startsWith("other");
    }
};

5.3. Mit einer benutzerdefinierten Anmerkung

Ein weiterer intelligenter Ansatz ist das Erstellen einer benutzerdefinierten Anmerkung:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {}

Wir können dannExclusionStrategy ausnutzen, damit es genau wie mit der Annotation@Exposefunktioniert, aber umgekehrt:

public class MyClass {
    private long id;
    private String name;
    @Exclude
    private String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    @Exclude
    private String otherVerboseInfo;
}

Und hier ist die Strategie:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getAnnotation(Exclude.class) != null;
    }
};

This StackOverflow answerhat diese Technik zuerst beschrieben.

Es ermöglicht uns, die Annotation und die Strategie einmal zu schreiben und unsere Felder ohne weitere Modifikation dynamisch mit Annotationen zu versehen.

5.4. Ausschlussstrategie auf Deserialisierung ausweiten

Unabhängig davon, welche Strategie wir verwenden, können wir jederzeit steuern, wo sie angewendet werden soll.

Nur während der Serialisierung:

Gson gson = new GsonBuilder().addSerializationExclusionStrategy(strategy)

Nur während der Deserialisierung:

Gson gson = new GsonBuilder().addDeserializationExclusionStrategy(strategy)

Immer:

Gson gson = new GsonBuilder().setExclusionStrategies(strategy);

6. Fazit

Wir haben verschiedene Möglichkeiten gesehen, Felder während der Gson-Serialisierung von einer Klasse und ihren Unterklassen auszuschließen.

Wir haben auch die wichtigsten Vorteile und Fallstricke jeder Lösung untersucht.

Wie immer ist der vollständige Quellcodeover on Github verfügbar.