通过配置 `com.fasterxml.jackson.databind.ObjectMapper` 的 `enableDefaultTyping` 方法,可以使序列化后的 JSON 包含类信息。

 

大家可能留意过,在redis里缓存的数据经常有下面两种形式。不难发现,这两者的区别就是后者包含了JavaObject类型信息。

  • {"levyName":"test","levyCode":1}
  • {"@class":"jstudy.jacksoncodec.JacksonObjectMapperTest$ChannelLevyDTO","levyName":"test","levyCode":1}


要让redis序列化时包含class类型,自然是对程序里定义的 com.fasterxml.jackson.databind.ObjectMapper实例进行设置。没错,我们使用它的 enableDefaultTyping(com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping, com.fasterxml.jackson.annotation.JsonTypeInfo.As) 方法。

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, 
   JsonTypeInfo.As.PROPERTY);

关于这个方法的第2个参数JsonTypeInfo.AsJsonTypeInfo是Jackson库里的一个注解,JsonTypeInfo.As枚举用于指定在 JSON 中包含类型信息的方式。 默认值是 JsonTypeInfo.As.PROPERTY

我们用上面代码中的 objectMapper 写个demo↓

ChannelLevyDTO channelLevyDTO = new ChannelLevyDTO1();
channelLevyDTO.levyCode=1;
channelLevyDTO.levyName="test";
String s = objectMapper.writeValueAsString(channelLevyDTO);

执行demo程序可以看到,s对应的json串是 {"@class":"jstudy.jacksoncodec.JacksonObjectMapperTest$ChannelLevyDTO","levyName":"test","levyCode":1}

JsonTypeInfo.As 枚举类型定义了以下几种类型信息包含方式:

  • WRAPPER_ARRAY:将类型信息包装在 JSON 数组中。
  • WRAPPER_OBJECT:将类型信息包装在 JSON 对象中。
  • PROPERTY:将类型信息作为属性添加到 JSON 中。
  • EXISTING_PROPERTY:将类型信息添加到现有的属性中。
  • EXTERNAL_PROPERTY:将类型信息作为外部属性引用。

上面demo使用的是As.PROPERTY。

使用As.WRAPPER_OBJECT则是:{"jstudy.jacksoncodec.JacksonObjectMapperTest$ChannelLevyDTO":{"levyName":"test","levyCode":1}}

使用As.WRAPPER_ARRAY 则是:["jstudy.jacksoncodec.JacksonObjectMapperTest$ChannelLevyDTO",{"levyName":"test","levyCode":1}]

补充一点,Jackson库ObjectMapper类弃用了enableDefaultTyping,取而代之的是 activateDefaultTyping 。

// /**/m3/com/fasterxml/jackson/core/jackson-databind/2.11.0/jackson-databind-2.11.0-sources.jar!/com/fasterxml/jackson/databind/ObjectMapper.java

/**
 * @deprecated Since 2.10 use {@link #activateDefaultTyping(PolymorphicTypeValidator,DefaultTyping,JsonTypeInfo.As)} instead
 */
@Deprecated
public ObjectMapper enableDefaultTyping(DefaultTyping applicability, JsonTypeInfo.As includeAs) {
    return activateDefaultTyping(getPolymorphicTypeValidator(), applicability, includeAs);
}

public ObjectMapper activateDefaultTyping(PolymorphicTypeValidator ptv,
        DefaultTyping applicability, JsonTypeInfo.As includeAs) {
    ...

    TypeResolverBuilder<?> typer = _constructDefaultTypeResolverBuilder(applicability, ptv);
    // we'll always use full class name, when using defaulting
    typer = typer.init(JsonTypeInfo.Id.CLASS, null);
    typer = typer.inclusion(includeAs);
    return setDefaultTyping(typer);
}

相比较可见,activateDefaultTyping 方法多了一个参数 PolymorphicTypeValidator,是用于验证多态类型信息的验证器。

activateDefaultTyping 这个方法允许我们指定如何处理多态类型信息以及如何在序列化和反序列化过程中包含类型信息。

需要说明的是,在未指定包括类型信息时,序列化后是仅包含数据的json串{"levyName":"test","levyCode":1},我们可以将一个JavaObjectA对象反序列化为JavaObjectB对象(前提是JavaObjectB包含JavaObjectA的所有field)。

而一旦在指定了包含类型信息后,将严格按照model类型进行反序列化。也就是说,这种情况下,把JavaObjectA反序列为JavaObjectB,程序会抛出异常:Could not resolve type id '%s' as a subtype of %s,其中第1个%s表示序列化对象JavaObjectA,第2个%s表示要反序列化的目标类型JavaObjectB↓

com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'jstudy.jacksoncodec.JacksonObjectMapperTest$ChannelLevyDTO' as a subtype of [simple type, class jstudy.jacksoncodec.JacksonObjectMapperTest$ChannelLevyDTO1]: Not a subtype