Gson:如何反序列化具有类名的接口字段? [英] Gson: How do I deserialize interface fields having class names?

查看:296
本文介绍了Gson:如何反序列化具有类名的接口字段?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试反序列化 Research 对象的 List ,但是我无法使其工作。我知道我需要一个自定义适配器来反序列化对象,因为我在我的 Research 类中使用了接口,但是我不确定如何实现它。

I am trying to deserialize a List of Research objects but I can't make it work. I know that I need a custom adapter to deserialize my object since I am using an interface in my Research class but I am unsure on how to implement it.

我目前有一个序列化程序,该序列化程序似乎可以工作并保存了用于反序列化的类类型。我一直在使用此SO帖子中的一些代码来制作序列化器: Gson反序列化其Class实现的接口

I currently have a serializer which seems to work and saves the class type for desirialization. I've been using bits of code from this SO post to make the serializer: Gson deserialize interface to its Class implementation

这是我正在使用的JSON:

This is the JSON I'm working with:

[
   {
      "bought":false,
      "cost":-20,
      "effect":{
         "amount":1,
         "className":"com.example.slarocque.cellclicker.Research.ResearchEffects.ClickAmountEffectStatic"
      },
      "name":"Better Flasks"
   },
   {
      "bought":false,
      "cost":-100,
      "effect":{
         "className":"com.example.slarocque.cellclicker.Research.ResearchEffects.ClickAmountEffectPercent",
         "percent":120
      },
      "name":"Buy a new Heater"
   },
   {
      "bought":false,
      "cost":-250,
      "effect":{
         "amount":2,
         "className":"com.example.slarocque.cellclicker.Research.ResearchEffects.ClickAmountEffectStatic"
      },
      "name":"Upgrade to Bacteria SuperFood"
   }
]

这就是 Research 基类:

public class Research implements Serializable {
    public String name;
    public int cost;
    public boolean bought = false;
    public IResearchEffect effect;

    public Research() {super();}

    public Research(String _name, int _points, IResearchEffect _effect, Boolean _bought){
        super();
        this.name = _name;
        this.cost = _points;
        this.effect = _effect;
        this.bought = (_bought == null ? false : _bought);
    }

    public void IsComplete() {
        this.bought = true;
    }

    @Override
    public String toString() {
        return this.name + " - " + this.cost;
    }
}

最后,这就是我要反序列化的方式我的Gson字符串:

Finally, this is how I'm trying to deserialize my Gson String:

    String json = settings.getString("List", null);
    List<Research> list = new ArrayList<>();

    //Make the GsonBuilder
    final GsonBuilder builder = new GsonBuilder();
    //builder.registerTypeAdapter(list.getClass(), /*Need adapter*/);

    final Gson gson = builder.create();
    Type listType = new TypeToken<List<Research>>() {}.getType();
    ResearchController.listResearch = gson.fromJson(json, listType);


推荐答案

除非您有足够的信息,否则无法直接反序列化接口必须如何实例化它们。您可以拥有 className 字段-可能足以获取所有内容。由于我有本地演示,而不是您的类,程序包等,因此您可以将下面的演示与您的代码对齐。

You cannot deserialize interfaces directly unless you have enough information on how they must be instantiated. It's fine that you have the className field -- it may be enough to get everything. Since I have a local demo, not your classes, packages, etc, you can align the demo below to your code.

这里没有什么特别的,只是概念验证使用 getValue()方法。

Nothing special here, just proof-of-concept using the getValue() method.

interface IResearchEffect {

    long getValue();

}

我认为以下自定义映射必须适合您的:

I consider the following custom mappings that must be adapted to yours:

final class ClickAmountEffectPercent
        implements IResearchEffect {

    final long percent = Long.valueOf(0);

    @Override
    public long getValue() {
        return percent;
    }

}



final class ClickAmountEffectStatic
        implements IResearchEffect {

    final long amount = Long.valueOf(0);

    @Override
    public long getValue() {
        return amount;
    }

}

请注意,我正在使用 final PRIMITIVE_TYPE VAR = WRAPPER_TYPE.valueOf(DEFAULT_VALUE)此处是为了禁用javac内联的原始常量值。与上面的映射类似,这是顶部映射:

Note that I'm using final PRIMITIVE_TYPE VAR = WRAPPER_TYPE.valueOf(DEFAULT_VALUE) here in order to disable primitive constant values inlining by javac. Similarly to the mappings above, here is the "top" mapping:

final class Research
        implements Serializable {

    final String name = null;
    final int cost = Integer.valueOf(0);
    final boolean bought = Boolean.valueOf(false);
    final IResearchEffect effect = null;

}

现在,核心部分:

final class ResearchEffectTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory researchEffectTypeAdapterFactory = new ResearchEffectTypeAdapterFactory();

    // Encapsulate the way it's instantiated
    private ResearchEffectTypeAdapterFactory() {
    }

    // ... not letting the caller to instantiate it with `new` -- it's a stateless singleton anyway, so one instance per application is FULLY legit
    static TypeAdapterFactory getResearchEffectTypeAdapterFactory() {
        return researchEffectTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Classes can be compared by == and !=
        // Note that we handle IResearchEffect only, otherwise we know that Gson has enought information itself
        if ( typeToken.getRawType() != IResearchEffect.class ) {
            return null;
        }
        // Create the type adapter for the IResearchEffect and cast it
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) new MyTypeAdapter(gson);
        return typeAdapter;
    }

    private static final class MyTypeAdapter
            extends TypeAdapter<IResearchEffect> {

        private final Gson gson;

        private MyTypeAdapter(final Gson gson) {
            this.gson = gson;
        }

        @Override
        public void write(final JsonWriter out, final IResearchEffect value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public IResearchEffect read(final JsonReader in) {
            // Since readers and writers are one-use only, you have to buffer the current value in an in-memory JSON tree
            final JsonElement jsonElement = gson.fromJson(in, JsonElement.class);
            // Extract the className property
            final String className = jsonElement.getAsJsonObject().get("className").getAsString();
            // And resolve the instantiation class
            // Note that I'm using switch here because I use another packages for this demo and I have to remap the original document strings to my demo mappings
            // You have to use something like gson.from(jsonElement, Class.forName(className));
            // Or whatever you prefer, but I would extract it as a strategy
            switch ( className ) {
            case "com.example.slarocque.cellclicker.Research.ResearchEffects.ClickAmountEffectStatic":
                return gson.fromJson(jsonElement, ClickAmountEffectStatic.class);
            case "com.example.slarocque.cellclicker.Research.ResearchEffects.ClickAmountEffectPercent":
                return gson.fromJson(jsonElement, ClickAmountEffectPercent.class);
            default:
                throw new IllegalArgumentException("Cannot instantiate " + className);
            }
        }

    }

}

演示:

// Note that TypeToken.getType() results can be considered value types thus being immutable and cached to a static final field
private static final Type researchesListType = new TypeToken<List<Research>>() {
}.getType();

// Gson is thread-safe as well, and can be used once per application
// Also, re-creating Gson instances would take more time due to its internals
private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getResearchEffectTypeAdapterFactory())
        .create();

public static void main(final String... args)
        throws IOException {
    try ( final Reader reader = getPackageResourceReader(Q43643447.class, "doc.json") ) {
        final List<Research> researches = gson.fromJson(reader, researchesListType);
        researches.forEach(research -> System.out.println(research.name + " " + research.effect.getValue()));
    }
}

输出:


更好的烧瓶1

购买新的Heater 120

升级到Bacteria SuperFood 2

Better Flasks 1
Buy a new Heater 120
Upgrade to Bacteria SuperFood 2

这篇关于Gson:如何反序列化具有类名的接口字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆