Skip to main content
Entwickler Themen
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

2.3. Gson

Hinweis: Die Codebeispiele orientieren sich am JAdventure-Projekt und können vom dortigen Stand abweichen.

JSON (JavaScript Object Notation) ist ein gängiges Textformat für Speicherung und Übertragung. Die Spezifikation steht bei json.org (extern).

Für Java sind Jackson und Gson die üblichen Bibliotheken. Hier geht es um Gson.

Gson in ein Maven-Projekt einbinden

<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.14.0</version>
</dependency>

Stand der Version: April 2026. Ob eine neuere vorliegt, zeigt die URL bei Maven Central.

Object → JSON

Gson gson = new Gson();
String jsonText = gson.toJson(someInstance, SomeType.class);

JSON → Object

Gson gson = new Gson();
SomeType someInstance = gson.fromJson(jsonText, SomeType.class);

Abgeleitete Klassen

Gson kennt zur Laufzeit nicht von selbst den konkreten Typ, wenn das Feld nur als Interface oder Basisklasse deklariert ist – etwa eine List<SavedObject>.

Ein möglicher Weg:

  • In der Basisklasse den konkreten Klassennamen als Feld mitschreiben.
  • Beim Serialisieren über einen Adapter immer den tatsächlichen Typ verwenden.
  • Beim Lesen zuerst den Typnamen auswerten und dann in genau diese Klasse deserialisieren.
Caution

Klassennamen in JSON sind ein Stabilitäts- und Sicherheitsrisiko: Umbenennungen brechen alte Dateien, und untrusted JSON kann beliebige Klassen laden. Für offene Schnittstellen ist ein explizites Typfeld (Whitelist) besser als Class.forName.

@JsonAdapter auf der Basisklasse plus context.serialize(object, object.getClass()) kann in eine Rekursion laufen, wenn Gson denselben Adapter erneut wählt. Dann den Adapter per GsonBuilder.registerTypeAdapter registrieren oder den Typ ohne Annotation serialisieren.

Für Listen braucht es denselben Mechanismus am Listentyp, etwa per eigener Adapter-Klasse und Annotation am Feld.

Basisklasse

package org.jadv.model;

import com.google.gson.annotations.JsonAdapter;
import lombok.Getter;
import org.jadv.serialization.SavedObjectAdapter;

/**
 * An instance that can be saved through an Adapter and contains the name of the class as field.
 */
@JsonAdapter(SavedObjectAdapter.class)
public abstract class SavedObject {
    /**
     * Class name of the
     */
    @Getter
    private final String type = getClass().getName();
}

JsonSerializer<SavedObject>

JsonSerializer<SavedObject> kann für alle Klassen, die von SavedObject erben, verwendet werden, um die Serialisierung durchzuführen.

    /**
     * Serializes an SavedObject to JSON. Makes sure that the serialization is using the correct class.
     * @param object Object to serialize.
     * @param interfaceType Not used.
     * @param context Serialization context.
     * @return The JsonElement that holds the serialized object.
     */
    @Override
    public JsonElement serialize(SavedObject object, Type interfaceType, JsonSerializationContext context) {
        if (object == null) return null;
        return context.serialize(object, object.getClass());
    }

JsonDeserializer<SavedObject>

JsonDeserializer<SavedObject> kann für jede Unterklasse von SavedObject verwendet werden. Zuerst wird der Klassenname gelesen, danach erfolgt die Deserialisierung für genau diesen Typ.

    /**
     * Deserializes the SavedInstance from a json.
     * @param elem Json element to deserialize
     * @param interfaceType not used.
     * @param context Deserialization context.
     * @return The restored instance with correct class.
     * @throws JsonParseException Throws an JsonParseException if deserialization is not possible.
     */
    @Override
    public SavedObject deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {
        final JsonObject wrapper = (JsonObject) elem;
        final JsonElement typeName = get(wrapper, "type");
        final Type actualType = typeForName(typeName);
        return context.deserialize(elem, actualType);
    }

Nutzung der Klasse

Um eine Klasse mit dem Adapter zu deserialisieren muss nur beim Deserialisieren als Zielklasse SavedObject.class angegeben werden. Der Adapter wird dann automatisch verwendet.

Zur Serialisierung muss sonst nichts weiter gemacht werden. Hier kann die korrekte Klasse angegeben werden oder auch SavedObject.class. Der Adapter sorgt dann automatisch für die korrekte Serialisierung.

Fertige Klasse mit Hilfsmethoden

package org.jadv.serialization;

import com.google.gson.*;
import org.jadv.model.SavedObject;

import java.lang.reflect.Type;

/**
 * Gson Adapter to (de-)serialize derived types.
 */
final public class SavedObjectAdapter implements JsonSerializer<SavedObject>, JsonDeserializer<SavedObject> {
    /**
     * Serializes an SavedObject to JSON. Makes sure that the serialization is using the correct class.
     * @param object Object to serialize.
     * @param interfaceType Not used.
     * @param context Serialization context.
     * @return The JsonElement that holds the serialized object.
     */
    @Override
    public JsonElement serialize(SavedObject object, Type interfaceType, JsonSerializationContext context) {
        if (object == null) return null;
        return context.serialize(object, object.getClass());
    }

    /**
     * Deserializes the SavedInstance from a json.
     * @param elem Json element to deserialize
     * @param interfaceType not used.
     * @param context Deserialization context.
     * @return The restored instance with correct class.
     * @throws JsonParseException Throws an JsonParseException if deserialization is not possible.
     */
    @Override
    public SavedObject deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {
        final JsonObject wrapper = (JsonObject) elem;
        final JsonElement typeName = get(wrapper, "type");
        final Type actualType = typeForName(typeName);
        return context.deserialize(elem, actualType);
    }

    /**
     * Gets the type for a given name.
     * @param typeElem JsonElement with classname inside.
     * @return The requested class.
     * @throws JsonParseException Thrown if the class is not available / known.
     */
    private Type typeForName(final JsonElement typeElem) throws JsonParseException {
        try {
            return Class.forName(typeElem.getAsString());
        } catch (ClassNotFoundException e) {
            throw new JsonParseException(e);
        }
    }

    /**
     * Gets an child element of an JsonObject,
     * @param wrapper Wrapper JsonObject to get the element from.
     * @param memberName Name of the element to get.
     * @return The requested JsonElement.
     * @throws JsonParseException Thrown if the requested element is not available.
     */
    private JsonElement get(final JsonObject wrapper, String memberName) throws JsonParseException {
        final JsonElement elem = wrapper.get(memberName);
        if (elem == null) throw new JsonParseException("no '" + memberName + "' member found in what was expected to be an interface wrapper");
        return elem;
    }
}

Serialisierung von Listen mit Subklassen

Bei der Serialisierung von einer Liste sollte jedes Element mit seiner eigenen Klasse serialisiert und auch wieder entsprechend deserialisiert werden. Dazu kann dann ein eigenes Adapter geschrieben werden:

package org.jadv.serialization;

import com.google.gson.*;
import org.jadv.model.SavedObject;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * Gson Adapter to (de-)serialize derived types.
 */
final public class ListOfSavedObjectAdapter implements JsonSerializer<List<SavedObject>>, JsonDeserializer<List<SavedObject>> {
    /**
     * Serializes a list of SavedInstance to JSON.
     * @param list Object to serialize.
     * @param interfaceType Not used.
     * @param context Serialization context.
     * @return The JsonElement that holds the serialized list.
     */
    public JsonElement serialize(List<SavedObject> list, Type interfaceType, JsonSerializationContext context) {
        if (list == null) return null;
        final JsonArray array = new JsonArray();
        for (SavedObject obj : list) {
            array.add(context.serialize(obj, SavedObject.class));
        }
        return array;
    }

    /**
     * Deserializes the list of SavedInstance from a json.
     * @param elem Json element to deserialize
     * @param interfaceType not used.
     * @param context Deserialization context.
     * @return The restored list with elements with correct class.
     * @throws JsonParseException Throws an JsonParseException if deserialization is not possible.
     */
    public List<SavedObject> deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {
        List<SavedObject> result = new ArrayList<>();
        final JsonArray array = (JsonArray) elem;
        for (JsonElement element : array.asList()) {
            result.add(context.deserialize(element, SavedObject.class));
        }
        return result;
    }
}