Custom Gson Serialization

I want to have my own GSON deserializer, so whenever it deserializes a JSON object (i.e. anything inside curly braces { ... }), it will look for $typenode and deserialize using the built-in deserialization of this type. If the object is $typenot found, it just does what it does normally.

So, for example, I would like this to work:

{
    "$type": "my.package.CustomMessage"
    "payload" : {
        "$type": "my.package.PayloadMessage",
        "key": "value"
    }
}

public class CustomMessage {

    public Object payload;
}

public class PayloadMessage implements Payload {

    public String key;
}

Call: Object customMessage = gson.fromJson(jsonString, Object.class).

So, if I changed the type payloadto an interface payload:

public class CustomMessage {

    public Payload payload;
}

Then the following TypeAdapaterFactorywill do what I want:

final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
final PojoTypeAdapter thisAdapter = this;

public T read(JsonReader reader) throws IOException {

    JsonElement jsonElement = (JsonElement)elementAdapter.read(reader);

    if (!jsonElement.isJsonObject()) {
        return delegate.fromJsonTree(jsonElement);
    }

    JsonObject jsonObject = jsonElement.getAsJsonObject();
    JsonElement typeElement = jsonObject.get("$type");

    if (typeElement == null) {
        return delegate.fromJsonTree(jsonElement);
    }

    try {
        return (T) gson.getDelegateAdapter(
                thisAdapter,
                TypeToken.get(Class.forName(typeElement.getAsString()))).fromJsonTree(jsonElement);
    } catch (ClassNotFoundException ex) {
        throw new IOException(ex.getMessage());
    }
}

However, I would like it to work when it payloadhas a type Objector any type in this regard, and throws some type matching exception if it cannot assign a variable.

+4
3

Gson, , , :

// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);

// user type adapters
factories.addAll(typeAdapterFactories);

, ObjectTypeAdapter factory.

, , , ObjectTypeAdapter factory . , .

+2

, Gson, Genson .

, :

Genson genson = new Genson.Builder().setWithClassMetadata(true).create();

:

Genson genson = new Genson.Builder().addAlias("myClass", my.package.SomeClass.class).create();

:

  • , , @class
  • json - ,
  • json, .
+2

This code skeleton works on your example, but it should be improved and tested in different scenarios.

public class PojoTypeAdapaterFactory implements TypeAdapterFactory {

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
        // check types we support
        if (type.getRawType().isAssignableFrom(CustomMessage.class) || type.getRawType().isAssignableFrom(PayloadMessage.class)) {
            return new PojoTypeAdapter<T>(gson, type);
        }
        else return null;
    }

    private class PojoTypeAdapter<T> extends TypeAdapter<T> {

        private Gson gson;

        private TypeToken<T> type;

        private PojoTypeAdapter(final Gson gson, final TypeToken<T> type) {
            this.gson = gson;
            this.type = type;
        }

        public T read(JsonReader reader) throws IOException {
            final TypeAdapter<T> delegate = gson.getDelegateAdapter(PojoTypeAdapaterFactory.this, this.type);
            final TypeAdapter<JsonElement> elementAdapter = this.gson.getAdapter(JsonElement.class);
            JsonElement jsonElement = elementAdapter.read(reader);

            if (!jsonElement.isJsonObject()) {
                return (T) this.gson.getAdapter(JsonElement.class).fromJsonTree(jsonElement);
            }

            JsonObject jsonObject = jsonElement.getAsJsonObject();
            JsonElement typeElement = jsonObject.get("$type");

            if (typeElement == null) {
                return delegate.fromJsonTree(jsonElement);
            }

            try {
                final Class myClass = Class.forName(typeElement.getAsString());
                final Object myInstance = myClass.newInstance();
                final JsonObject jsonValue = jsonElement.getAsJsonObject().get("value").getAsJsonObject();
                for (Map.Entry<String, JsonElement> jsonEntry : jsonValue.entrySet()) {
                    final Field myField = myClass.getDeclaredField(jsonEntry.getKey());
                    myField.setAccessible(true);
                    Object value = null;
                    if (jsonEntry.getValue().isJsonArray()) {
                        //value = ...;
                    }
                    else if (jsonEntry.getValue().isJsonPrimitive()) {
                        final TypeAdapter fieldAdapter = this.gson.getAdapter(myField.getType());
                        value = fieldAdapter.fromJsonTree(jsonEntry.getValue());
                    }
                    else if (jsonEntry.getValue().isJsonObject()) {
                        value = this.fromJsonTree(jsonEntry.getValue());
                    }
                    myField.set(myInstance, value);
                }
                return (T) myInstance;

            }
            catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InstantiationException | NoSuchFieldException | SecurityException e) {
                throw new IOException(e);
            }
        }

        @Override
        public void write(final JsonWriter out, final T value) throws IOException {
            out.beginObject();
            out.name("$type");
            out.value(value.getClass().getName());
            out.name("value");
            final TypeAdapter<T> delegateAdapter = (TypeAdapter<T>) this.gson.getDelegateAdapter(PojoTypeAdapaterFactory.this, TypeToken.<T>get(value.getClass()));
            delegateAdapter.write(out, value);
            out.endObject();
        }
    }

}

The generated JSON is not quite the same as it contains an extra entry value:

{
  "$type": "my.package.CustomMessage",
  "value": {
    "payload": {
      "$type": "my.package.PayloadMessage",
      "value": {
        "key": "hello"
      }
    }
  }
}
0
source

Source: https://habr.com/ru/post/1540840/


All Articles