Gson:如何反序列化具有类名的接口字段?

时间:2017-04-26 19:56:47

标签: java gson deserialization

我正在尝试对List ResearchResearch个对象进行反序列化,但我无法使其正常工作。我知道我需要一个自定义适配器来反序列化我的对象,因为我在[ { "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" } ] 类中使用了一个接口,但我不确定如何实现它。

我目前有一个似乎可以工作的序列化程序,并保存了类型的desirialization。我一直在使用SO帖子中的一些代码来制作序列化器:Gson deserialize interface to its Class implementation

这是我正在使用的JSON:

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; } } 基类:

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

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

Amount
1. $100.00
2. $25.23
3. $24.00
4. $12.43

Total: $184.43

2 个答案:

答案 0 :(得分:2)

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

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

interface IResearchEffect {

    long getValue();

}

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

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禁用内联常量值内联。与上面的映射类似,这里是“顶部”映射:

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
  购买新的加热器120
  升级到Bacteria SuperFood 2

答案 1 :(得分:0)

试试这个:

Gson gson = new Gson();
ResearchController.listResearch = gson.fromJson(object.toString(), YourModel.class).getList();

YourModel应该是一个模型,里面有元素列表,并带有getter(getList())。