【问题标题】:Gson: How do I deserialize interface fields having class names?Gson:如何反序列化具有类名的接口字段?
【发布时间】:2017-04-26 19:56:46
【问题描述】:

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

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

这是我正在使用的 JSON:

[
   {
      "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 字符串的方式:

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

【问题讨论】:

    标签: java gson deserialization


    【解决方案1】:

    你不能直接反序列化接口,除非你有足够的信息来说明它们必须如何被实例化。拥有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

    【讨论】:

      【解决方案2】:

      试试这个:

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

      YourModel 应该是一个包含元素列表的模型,其中包含一个 getter (getList())。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-04
        • 2011-12-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多