java - Map 子类的 Gson 序列化,也具有显式定义的属性

标签 java json serialization gson jsonserializer

我有一组生成的 java beans,其中每个 bean 定义一个或多个字段,加上它的子类 HashMap<String, T>哪里T是参数化类型。生成的字段模型在 JSON schema 中显式定义的架构属性,以及 HashMap 的子类这样做是为了支持特定类型的附加“任意”属性(对于熟悉 JSON 架构的人来说,由 JSON 架构的“additionalProperties”字段指定)。 下面是生成的 bean 的示例:

public class MyModel extends HashMap<String, Foo> {
    private String prop1;
    private Long prop2;

    public String getProp1() {
        return prop1;
    }

    public void setProp1(String value) {
        this.prop1 = value;
    }

    public Long getProp2() {
        return prop2;
    }

    public void setProp2(Long prop2) {
        this.prop2 = prop2;
    }
}

在此示例中,用户可以设置 prop1prop2作为普通 bean 属性,还可以设置 Foo 类型的任意属性通过Mapput()方法,其中 Foo只是一些其他用户定义的类型。

问题是,默认情况下,Gson序列化这些生成的 bean 的实例,以便只有 Map条目包含在结果 JSON 中字符串,显式定义的字段将被忽略。

这是一个代码片段,展示了我如何使用 Gson序列化对象:

    private String serialize(Object obj) {
        return new Gson().toJson(obj);
    }

通过调试序列化路径,我可以看到Gson正在选择其内部 MapTypeAdapterFactory执行序列化,这是有意义的,因为只有 Map 条目最终会出现在 JSON 字符串中。 另外,如果我序列化一个不属于 HashMap 子类的 bean,则 Gson选择其内部 ReflectiveTypeAdapterFactory相反。

我认为我需要的是实现我自己的自定义类型适配器,它本质上结合了 Reflective 的功能和Map类型适配器工厂。 这听起来是个好计划吗?有没有其他人做过类似的事情并且可以提供一个例子来帮助我开始?这将是我第一次涉足 Gson自定义类型适配器。

最佳答案

我们知道,默认情况下,Gson 将这些对象视为 Map,因此我们可以使用它来序列化所有键值 对,并使用 reflection 手动序列化其余部分。 。

简单的序列化器实现如下所示:

class MixedJsonSerializer implements JsonSerializer<Object> {

    @Override
    public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject json = serialiseAsMap(src, context);
        serialiseAsPojo(src, context, json);

        return json;
    }

    private JsonObject serialiseAsMap(Object src, JsonSerializationContext context) {
        return (JsonObject) context.serialize(src, Map.class);
    }

    private void serialiseAsPojo(Object src, JsonSerializationContext context, JsonObject mapElement) {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(src.getClass());
        for (Method method : methods) {
            if (shouldSerialise(method)) {
                final Object result = ReflectionUtils.invokeMethod(method, src);
                final String fieldName = getFieldName(method);
                mapElement.add(fieldName, context.serialize(result));
            }
        }
    }

    private boolean shouldSerialise(Method method) {
        final String name = method.getName();

        return method.getParameterCount() == 0 &&
                ReflectionUtils.USER_DECLARED_METHODS.matches(method) &&
                !IGNORED_METHODS.contains(name) &&
                (name.startsWith("is") || name.startsWith("get"));
    }

    private static final List<String> IGNORED_METHODS = Arrays.asList("isEmpty", "length"); //etc

    private String getFieldName(Method method) {
        final String field = method.getName().replaceAll("^(is|get)", "");

        return StringUtils.uncapitalize(field);
    }
}

最复杂的部分是找到所有 POJO getter 并在给定对象上调用它们。我用过reflection API例如来自 Spring 库。您可以在下面找到如何使用它的示例(我假设所有 POJO 类都扩展了 HashMap):

import com.model.Foo;
import com.model.Pojo;
import com.model.Pojo1;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;

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

public class GsonApp {

    public static void main(String[] args) {
        System.out.println("Pojo + Map: ");
        Pojo pojo = new Pojo();
        pojo.put("character1", new Foo("Morty", 15));
        pojo.put("character2", new Foo("Rick", 60));

        System.out.println(serialize(pojo));
        System.out.println();

        System.out.println("Map only: ");
        Pojo1 pojo1 = new Pojo1();
        pojo1.put("int1", 1);
        pojo1.put("int2", 22);
        System.out.println(serialize(pojo1));
        System.out.println();

        System.out.println("Pojo only:");
        System.out.println(serialize(new Pojo()));
        System.out.println();
    }

    private static final Gson gson = createGson();

    private static Gson createGson() {
        MixedJsonSerializer adapter = new MixedJsonSerializer();
        return new GsonBuilder()
                .setPrettyPrinting()
                // in case you have many classes you need to use reflection
                // to register adapter for each needed class.
                .registerTypeAdapter(Pojo.class, adapter)
                .registerTypeAdapter(Pojo1.class, adapter)
                .create();
    }

    private static String serialize(Object obj) {
        return gson.toJson(obj);
    }
}

上面的代码打印:

Pojo + Map: 
{
  "character2": {
    "name": "Rick",
    "age": 60
  },
  "character1": {
    "name": "Morty",
    "age": 15
  },
  "prop1": "Value1",
  "ten": 10,
  "foo": {
    "name": "Test",
    "age": 123
  }
}

Map only: 
{
  "int2": 22,
  "int1": 1
}

Pojo only:
{
  "prop1": "Value1",
  "ten": 10,
  "foo": {
    "name": "Test",
    "age": 123
  }
}

如果您需要手动注册许多类,您可以使用反射来扫描给定的包并为它们注册序列化程序。请参阅:Can you find all classes in a package using reflection?

关于java - Map 子类的 Gson 序列化,也具有显式定义的属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55636226/

相关文章:

java - ObjectInputStream 的工作原理

java - 正则表达式 - 搜索一个单词并使用不区分大小写的相同单词替换它

java - 如何使用 HashMap 模拟我的 Mongodb 存储库

java - 映射到 XML 属性的 PostgreSQL 字段

json - Node 包 - 是否可以在 package.json 中插入变量?

java - 我可以使用 FileChooser (javafx) 将序列化对象保存在文件中吗?

java - 使用 gwt-visualization 并排绘制图表

java - Java 中的控制台

java - 在 REST/Java 中,如果我的对象为 null,我应该返回什么?

java - 从一项 Activity 传递对象时出现问题