【问题标题】:GSON not including discriminator field when basic List of objectsGSON 在基本对象列表时不包括鉴别器字段
【发布时间】:2017-06-01 13:25:37
【问题描述】:

我在当前项目中遇到了 GSON 多态对象的奇怪情况。情况是这样的:我有两个不同的抽象基类,有两个不同的用例:

  • 第一个类包含在一个 List 中,并且
  • 第二个类也包含在 List 中,但该列表是较大父对象的一部分

人为类的简化版本(为简洁起见,省略了构造函数和访问器;定义了鉴别器字段但为了说明而注释掉了):

public abstract class ClassNumeric {
    //private String numericType;
}

public class ClassOne extends ClassNumeric {
   private String hex;
}

public class ClassTwo extends ClassNumeric {
    private Integer whole;
    private Integer fraction;
}

public abstract class ClassAlphabetic {
    //private String alphabeticType;
}

public class ClassAlpha extends ClassAlphabetic {
    private String name;
}

public class ClassBravo extends ClassAlphabetic {
    private Integer age;
    private Integer numberOfMarbles;
}

public class Container {
    private String group;
    private List<ClassAlphabetic> alphabetics;
}

适配器工厂及其在 GSON 中的注册:

public RuntimeTypeAdapterFactory<ClassNumeric> numericTypeFactory = RuntimeTypeAdapterFactory
    .of(ClassNumeric.class, "numericType")
    .registerSubtype(ClassOne.class)
    .registerSubtype(ClassTwo.class);

public RuntimeTypeAdapterFactory<ClassAlphabetic> alphabeticTypeFactory = RuntimeTypeAdapterFactory
    .of(ClassAlphabetic.class, "alphabeticType")
    .registerSubtype(ClassAlpha.class)
    .registerSubtype(ClassBravo.class);

public final Gson gson = new GsonBuilder()
    .setPrettyPrinting()
    .disableHtmlEscaping()
    .registerTypeAdapterFactory(numericTypeFactory)
    .registerTypeAdapterFactory(alphabeticTypeFactory)
    .create();

根据我目前所读到的内容,我不必(实际上不应该)在基类中声明鉴别器字段,因为 GSON 在内部处理这些字段,因为 JSON 被序列化和反序列化。

以下是如何使用它们的示例:

ClassOne c1 = ClassOne.builder().hex("EF8A").build();
ClassTwo c2 = ClassTwo.builder().whole(1).fraction(3).build();
List<ClassNumeric> numerics = Arrays.asList(c1, c2);   // List of child objects
log.debug("Numerics: " + gson.toJson(numerics));

ClassAlpha ca = ClassAlpha.builder().name("Fred").build(); 
ClassBravo cb = ClassBravo.builder().age(5).numberOfMarbles(42).build();
List<ClassAlphabetic> alphas = Arrays.asList(ca, cb);
Container container = Container.builder().group("Test Group 1").alphabetics(alpha).build();  // List of objects field on larger object
log.debug("Alphas (container): " + gson.toJson(container));

我遇到的问题是 ClassAlphabetic 对象工作正常(鉴别器字段存在于 JSON 中),而 ClassNumeric 对象却没有(鉴别器字段缺失)。示例输出:

09:12:17.910 [main] DEBUG c.s.s.s.s.GSONPolymorphismTest - Numerics: [
    {
        "hex": "EF8A"
    },
    {
        "whole": 1,
        "fraction": 3
    }
]
09:12:17.926 [main] DEBUG c.s.s.s.s.GSONPolymorphismTest - Alphas (container): {
    "group": "Test Group 1",
    "alphabetics": [
        {
            "alphabeticType": "ClassAlpha",
            "name": "Fred"
        },
        {
            "alphabeticType": "ClassBravo",
            "age": 5,
            "numberOfMarbles": 42
        }
    ]
}

我在这里缺少什么?这些本质上是通过 GSON 以相同的方式定义和设置的,但一个用例适用于另一个用例。

【问题讨论】:

    标签: java polymorphism gson


    【解决方案1】:

    这是因为泛型在 Java 中的工作方式。简而言之,特定的泛型类实例没有任何类型参数化信息作为实例的一部分。然而,类型参数可以存储在字段类型、方法返回类型、方法参数、继承的超类(例如,extends ArrayList&lt;Integer&gt;)、自定义参数化类型信息实例等中。局部变量,如 numerics 是,保持类型编译期间的参数并存在于您和编译器的脑海中——由于type erasure。所以,它就像运行时的​​原始List。与numerics 类似,alphas 没有任何运行时参数化,但与局部变量不同,Container.alphabetics 字段具有保存在运行时中的类型信息——字段可以提供完整的类型信息. Gson 使用它来确定应用哪种(反)序列化策略。类似地,当没有提供额外的类型参数信息(如局部变量)时,Gson 使用 default 策略。正如我上面提到的,您还可以创建自定义参数化类型ParameterizedType 来提供原始类型及其类型参数信息。它有什么帮助?如果您仔细查看 Gson toJson 重载,您会发现其中一个重载 accepts 是一个附加参数(我选择了最简单的一个)。这可以被认为是对 Gson 的一种提示,告诉它传递的实例的确切类型(不一定匹配,但在大多数情况下应该匹配)。所以,为了让它工作,告诉 Gson 你的 numerics List 类型参数化:

    // "Canonical" way: TypeToken analyzes its superclass parameterization and returns its via the getType method
    private static final Type classNumericListType = new TypeToken<List<ClassNumeric>>() {
    }.getType()));
    
    System.out.println("Numerics: " + gson.toJson(numerics, classNumericListType);
    

    或者,如果你可以使用 Gson 2.8.0+,

    private static final Type classNumericListType = TypeToken.getParameterized(List.class, ClassNumeric.class);
    
    System.out.println("Numerics: " + gson.toJson(numerics, classNumericListType);
    

    或者简单地创建您自己的ParameterizedType 实现。当类型在编译时已知时,我将使用类型标记,如果类型仅在运行时已知,则使用 TypeToken.getParameterized。这样,传递类型实例就会触发RuntimeTypeAdapterFactory 机制(现在是ClassNumeric,而不是原始Object),所以输出如下:

    Numerics: [
      {
        "numericType": "N1",
        "hex": "EF8A"
      },
      {
        "numericType": "N2",
        "whole": 1,
        "fraction": 3
      }
    ]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-05-05
      • 1970-01-01
      • 1970-01-01
      • 2016-01-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多