使用通用通配符时,如何解决Gson序列化提供不同结果的问题? [英] How to work around Gson serialization giving different results when using generic wildcards?
问题描述
请考虑以下示例:
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屋!