java - 使用通用通配符时如何解决 Gson 序列化给出不同结果的问题?

标签 java json serialization 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":[{"childField":"child","baseField":"base"}]}

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

我还看到了其他问题,比如 here但我想知道是否有合理的解决方法来实现我想要的。

换句话说:有没有办法让这样的一个“holder”类接受 BaseBeans 或 ChildBeans(<? extends BaseBean> 这样做),并且 当使用 Gson 将实例序列化为 JSON 字符串时给我正确的结果?

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

最佳答案

通常集合实现从集合字段声明中“获取”类型——而不是来自 List 上的给定项目/Set/ETC。我们需要编写自定义序列化器,为每个项目找到序列化器并使用它。简单实现:

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"
    }
  ]
}

编辑

在序列化过程中,我们丢失了反序列化过程中需要的类型信息。我开发了简单的类型信息,这些信息将在序列化期间存储并用于反序列化。它可能如下所示:

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>并使用它。现在,我们可以像上面那样使用它。示例应用现在打印:

{
  "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}

关于java - 使用通用通配符时如何解决 Gson 序列化给出不同结果的问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55222355/

相关文章:

java - java中反序列化步骤

c# - Json.Net 从驼峰表示法反序列化为标准 .Net 属性表示法(无属性)

java - 如何将类分配给graphstream中的节点

ios - 通缉 : Up-to-date example for JSON/POST with basic auth using AFNetworking-2

c# - ASP MVC、延迟加载和 JSON 序列化。如何填写其他属性?

java - 如何使用 java sdk 将嵌套的 json 对象存储和更新到 couchbase

json - Kubernetes (kubectl) 获取正在运行的 Pod

java - 如何立即识别 Java 中断开的套接字连接?

java - 解释这个 for 循环片段

java - 如何在broadleaf框架中导出数据库?