使用通用通配符时,如何解决Gson序列化提供不同结果的问题? [英] How to work around Gson serialization giving different results when using generic wildcards?

查看:83
本文介绍了使用通用通配符时,如何解决Gson序列化提供不同结果的问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下示例:

static class BaseBean { String baseField = "base"; }
static class ChildBean extends BaseBean { String childField = "child"; }

static class BaseBeanHolder {
    List <? extends BaseBean> beans;

    public BaseBeanHolder(List<? extends BaseBean> beans) { this.beans = beans; }
}

static class ChildBeanHolder {
    List <ChildBean> beans;

    public ChildBeanHolder(List<ChildBean> beans) { this.beans = beans; }
}

@Test
public void mcve() {
    BaseBeanHolder baseHolder = new BaseBeanHolder(singletonList(new ChildBean()));
    System.out.println(new Gson().toJson(baseHolder));

    ChildBeanHolder childHolder = new ChildBeanHolder(singletonList(new ChildBean()));
    System.out.println(new Gson().toJson(childHolder));
}

它打印:

{"beans":[{"baseField":"base"}]}

{"beans":[{"baseField":"base"}]}

{"beans":[{"childField":"child","baseField":"base"}]}

{"beans":[{"childField":"child","baseField":"base"}]}

因此,尽管两个列表都包含子对象,但是只有第二个所有者会导致将子字段序列化为JSON.

So, although both lists hold child objects, only the second holder results in the child fields being serialized to JSON.

我还看到了其他问题,例如此处,但我想知道是否存在合理的解决方法来实现目标我想要.

I have seen other questions, like here but I wondering whether there are reasonable workarounds to achieve what I want.

换句话说:有没有办法让这样的一个持有人"类可以接受BaseBeans或ChildBeans(<? extends BaseBean>可以做到),并且可以在将带有Gson的实例序列化为JSON字符串时为我提供正确的结果吗?

In other words: is there a way to have such one "holder" class that accepts either BaseBeans or ChildBeans (the <? extends BaseBean> does that), and that also gives me the correct results when serialising instances with Gson into JSON strings?

(注意:我无法使用特定的类型适配器,因为我无法控制实际的Gson实例来自何处以及在我们的环境中如何配置它)

( note: I can't use specific type adapters, as I have no control where that actual Gson instance is coming from and how it is configured in our environment )

推荐答案

通常,集合实现是从集合字段声明中获取"类型的,而不是从List/Set/etc中的给定项目中获得的.我们需要编写自定义序列化程序,该序列化程序可以为每个项目找到序列化程序并使用它.简单的实现:

Generally collection implementations "takes" type from collection field declaration - not from given item on the List/Set/etc. We need to write custom serialiser which for each item find serialiser and use it. Simple implementation:

class TypeAwareListJsonSeserializer implements JsonSerializer<List<?>> {
    @Override
    public JsonElement serialize(List<?> src, Type typeOfSrc, JsonSerializationContext context) {
        if (src == null) {
            return JsonNull.INSTANCE;
        }
        JsonArray array = new JsonArray();
        for (Object item : src) {
            JsonElement jsonElement = context.serialize(item, item.getClass());
            array.add(jsonElement);
        }
        return array;
    }
}

这是我们如何使用它:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.JsonAdapter;

import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;

public class GsonApp {

    public static void main(String[] args) throws Exception {
        List<BaseBean> children = Arrays.asList(new BaseBean(), new ChildBean(), new ChildBean2());
        BaseBeanHolder baseHolder = new BaseBeanHolder(children);
        Gson gson = new GsonBuilder()
                .setPrettyPrinting()
                .create();
        System.out.println(gson.toJson(baseHolder));
    }
}

class BaseBean {
    String baseField = "base";
}

class ChildBean extends BaseBean {
    String childField = "child";
}

class ChildBean2 extends BaseBean {
    int bean2Int = 356;
}

class BaseBeanHolder {

    @JsonAdapter(TypeAwareListJsonSeserializer.class)
    private List<? extends BaseBean> beans;

    // getters, setters, toString
}

上面的代码显示:

{
  "beans": [
    {
      "baseField": "base"
    },
    {
      "childField": "child",
      "baseField": "base"
    },
    {
      "bean2Int": 356,
      "baseField": "base"
    }
  ]
}

编辑

在序列化过程中,我们会丢失有关反序列化过程中所需类型的信息.我开发了简单的类型信息,该信息将在序列化过程中存储并用于反序列化.可能如下所示:

EDIT

During serialisation we lose information about type which will be needed during deserialisation process. I developed simple type information which will be stored during serialisation and used in deserialisation. It could look like below:

class TypeAwareListJsonAdapter implements JsonSerializer<List<?>>, JsonDeserializer<List<?>> {

    private final String typeProperty = "@type";

    @Override
    public JsonElement serialize(List<?> src, Type typeOfSrc, JsonSerializationContext context) {
        if (src == null) {
            return JsonNull.INSTANCE;
        }
        JsonArray array = new JsonArray();
        for (Object item : src) {
            JsonObject jsonElement = (JsonObject) context.serialize(item, item.getClass());
            jsonElement.addProperty(typeProperty, item.getClass().getSimpleName());

            array.add(jsonElement);
        }
        return array;
    }

    @Override
    public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        final Type elementType = $Gson$Types.getCollectionElementType(typeOfT, List.class);

        if (json instanceof JsonArray) {
            final JsonArray array = (JsonArray) json;
            final int size = array.size();
            if (size == 0) {
                return Collections.emptyList();
            }

            final List<?> suites = new ArrayList<>(size);
            for (int i = 0; i < size; i++) {
                JsonObject jsonElement = (JsonObject) array.get(i);
                String simpleName = jsonElement.get(typeProperty).getAsString();
                suites.add(context.deserialize(jsonElement, getClass(simpleName, elementType)));
            }

            return suites;
        }

        return Collections.emptyList();
    }

    private Type getClass(String simpleName, Type defaultType) {
        try {
            // you can use mapping or something else...
            return Class.forName("com.model." + simpleName);
        } catch (ClassNotFoundException e) {
            return defaultType;
        }
    }
}

最大的问题是如何将类映射到JSON值.我们可以使用类的简单名称或提供Map<String, Class>并使用它.现在,我们可以像上面一样使用它.现在可以打印示例应用程序:

The biggest problem is to how to map classes to JSON values. We can use class simple name or provide Map<String, Class> and use it. Now, we can use it as above. Example app prints now:

{
  "beans": [
    {
      "baseField": "base",
      "@type": "BaseBean"
    },
    {
      "childField": "child",
      "baseField": "base",
      "@type": "ChildBean"
    },
    {
      "bean2Int": 356,
      "baseField": "base",
      "@type": "ChildBean2"
    }
  ]
}
BaseBean{baseField='base'}
ChildBean{baseField='base', childField='child'}
ChildBean2{baseField='base', bean2Int=356}

这篇关于使用通用通配符时,如何解决Gson序列化提供不同结果的问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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