Gson custom serialization

前提是你 提交于 2021-02-08 16:53:48

问题


I wish to have a custom GSON deserializer such that whenever it is deserializing a JSON object (i.e. anything within curly brackets { ... }), it will look for a $type node and deserialize using its inbuilt deserializing capability to that type. If no $type object is found, it just does what it normal does.

So for example, I would want 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;
}

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

So currently if I change the payload type to the Payload interface:

public class CustomMessage {

    public Payload payload;
}

Then the following TypeAdapaterFactory will 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 payload is of type Object or any type for that matter, and throw some sort of type match exception if it can't assign the variable.


回答1:


I don't know how you can achieve it with Gson but you have such a feature in Genson by default.

To enable it just do:

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

You can also register aliases for your class names:

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

This has however some limitations:

  • at the moment you can't change the key used to identify the type, it is @class
  • it must be present in your json before the other properties - but looks fine as it is the case in your examples
  • Works only with json objects and not arrays or litterals



回答2:


Looking at the source for Gson, I have found what I think is the issue:

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

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

As you can see the ObjectTypeAdapter will take precedence over my factory.

The only solution as far as I can see is to use reflection to remove the ObjectTypeAdapter from the list or insert my factory before it. I have done this and it works.




回答3:


This code skeleton works on your example but should be improved and tested with 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 exactly the same though, as it contains an additional value entry:

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


来源:https://stackoverflow.com/questions/23695649/gson-custom-serialization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!