【问题标题】:Gson custom serializationGson 自定义序列化
【发布时间】:2014-05-16 13:01:24
【问题描述】:

我希望有一个自定义的 GSON 反序列化器,这样每当它反序列化 JSON 对象(即大括号 { ... } 内的任何内容)时,它都会查找 $type 节点并使用其内置的反序列化功能反序列化该类型.如果没有找到$type 对象,它只会做正常的事情。

例如,我希望它能够工作:

{
    "$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;
}

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

所以目前如果我将payload 类型更改为Payload 接口:

public class CustomMessage {

    public Payload payload;
}

那么下面的TypeAdapaterFactory会做我想做的事:

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());
    }
}

但是,我希望它在 payload 的类型为 Object 或任何类型时工作,如果它无法分配变量,则抛出某种类型匹配异常。

【问题讨论】:

  • 您能否详细说明您的解决方案无法解决的问题?
  • @DamianWalczak - 结果表明它需要反序列化为CustomMessage ok,但它实际上并没有命中有效负载的 typeadapterfactory...它只是使用其内置的反序列化

标签: java json serialization gson


【解决方案1】:

查看 Gson 的来源,我发现了我认为的问题:

// 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);

如您所见,ObjectTypeAdapter 将优先于我的工厂。

据我所知,唯一的解决方案是使用反射从列表中删除 ObjectTypeAdapter 或在它之前插入我的工厂。我已经这样做了,它有效。

【讨论】:

    【解决方案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 对象,不适用于数组或文字

    【讨论】:

    • 我认为泛型不会那样工作?例如“地图
    • 这个特性主要是为有继承性的pojo设计的。但可能也可以扩展到使用地图(它与泛型无关,而是与 json 表示有关)。
    【解决方案3】:

    此代码框架适用于您的示例,但应针对不同场景进行改进和测试。

    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();
            }
        }
    
    }
    

    生成的 JSON 并不完全相同,因为它包含一个额外的 value 条目:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-04
      • 2016-01-16
      • 2020-01-23
      相关资源
      最近更新 更多