Jackson忽略注解序列化枚举类型遇到空指针异常

最近在使用Jackson的时候遇到‘忽略注解序列化枚举类型抛空指针异常’下面贴部分代码,Jackson版本1.9.10

枚举对象

public enum ViewTime
{
    FOREVER(-1), SEVEN_DAY(0), ONE_MONTH(1),TWO_MONTH(2),THREE_MONTH(3),SIX_MONTH(4),ONE_YEAR(5);
    
    private int value;

    private ViewTime(int value)
    {
        this.value = value;
    }

    public int getValue()
    {
        return value;
    }
    
    public static ViewTime fromValue(int period)
    {
        switch(period)
        {
        case -1:
            return FOREVER;
        case 0:
            return SEVEN_DAY;
        case 1:
            return ONE_MONTH;
        case 2:
            return TWO_MONTH;
        case 3:
            return THREE_MONTH;
        case 4:
            return SIX_MONTH;
        case 5:
            return ONE_YEAR;
        }
        return null;
    }
    @Override
    public String toString()
    {
        return String.valueOf(value);
    }
}
测试代码

        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationConfig.Feature.USE_ANNOTATIONS, false);
        mapper.setAnnotationIntrospector(new NopAnnotationIntrospector());
        System.out.println(mapper.writeValueAsBytes(ViewTime.FOREVER));
下面是异常信息

org.codehaus.jackson.map.JsonMappingException: [no message for java.lang.NullPointerException]
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:625)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256)
at org.codehaus.jackson.map.ObjectMapper._configAndWriteValue(ObjectMapper.java:2575)
at org.codehaus.jackson.map.ObjectMapper.writeValueAsBytes(ObjectMapper.java:2114)
at com.myproduct.mobileinternet.cmsinterface.pojo.Configure.main(Configure.java:61)
Caused by: java.lang.NullPointerException
at org.codehaus.jackson.smile.SmileGenerator.writeString(SmileGenerator.java:996)
at org.codehaus.jackson.map.ser.std.EnumSerializer.serialize(EnumSerializer.java:59)
at org.codehaus.jackson.map.ser.std.EnumSerializer.serialize(EnumSerializer.java:24)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:610)
... 4 more

而只要注掉

mapper.configure(SerializationConfig.Feature.USE_ANNOTATIONS, false);
mapper.setAnnotationIntrospector(new NopAnnotationIntrospector());

这两行 则可以正常序列化。

我们可以到jackson官网上下源代码跟进去看看,最终定位到枚举处理类EnumValues。

是否忽略注解的差异在该类的constructFromName(Class<Enum<?>> enumClass, AnnotationIntrospector intr)方法

   public static EnumValues constructFromName(Class<Enum<?>> enumClass, AnnotationIntrospector intr)
    {
        /* [JACKSON-214]: Enum types with per-instance sub-classes
         *   need special handling
         */
    	Class<? extends Enum<?>> cls = ClassUtil.findEnumType(enumClass);
        Enum<?>[] values = cls.getEnumConstants();
        if (values != null) {
            // Type juggling... unfortunate
            Map<Enum<?>,SerializedString> map = new HashMap<Enum<?>,SerializedString>();
            for (Enum<?> en : values) {
                String value = intr.findEnumValue(en);
                map.put(en, new SerializedString(value));
            }
            return new EnumValues(map);
        }
        throw new IllegalArgumentException("Can not determine enum constants for Class "+enumClass.getName());
    }
如果忽略注解,红色部分intr调用的是NopAnnotationIntrospector类的findEnumValue()方法如下
    @Override
    public String findEnumValue(Enum<?> value) {
        return null;
    }
而不忽略注解的话,红色部分 intr调用的是 JacksonAnnotationIntrospector类的findEnumValue()方法如下

    @Override
    public String findEnumValue(Enum<?> value)
    {
        return value.name();
    }

问题就出在这里。

所以第一种解决办法--就是修改NopAnnotationIntrospector的源码findEnumValue改成和JacksonAnnotationIntrospector的一致

这样序列出来的值为枚举的name,譬如ViewTime.FOREVER序列出来的字符串为"FOREVER",这样可以满足大部分的需求了

但是往往我们用枚举并不想让序列成这种形式,譬如ViewTime.FOREVER我们想序列成-1,这该怎么办呢?

可能我们第一反应还是修改NopAnnotationIntrospector的源码findEnumValue,让他返回-1字符串,不过比较遗憾的是,findEnumValue的参数value没有获取值的方法。

就算有,可以序列成-1了,这里还有一个问题,在你反序列化的时候mapper.readValue(new String("-1"), ViewTime.class),很遗憾的告诉你,这里jackson把数字型的

字符串当下标算了。

也许你想到了序列化的时候返回下标,暂且不考虑返回下标合理不合理,这样看来总算可以正确的序列反序列化了。

举个例子,会不会有这样一种情况,序列化ViewTime.FOREVER的时候返回下标0,而在反序列化的时候ViewTime变成了这样

NONE(-5),FOREVER(-1), SEVEN_DAY(0), ONE_MONTH(1),TWO_MONTH(2),THREE_MONTH(3),SIX_MONTH(4),ONE_YEAR(5);

此时反序列化得到的是NONE?

下面说第二种解决办法--采用注解的方法来自定义序列化和反序列化类

@JsonSerialize(using = SubscribePeriodSerializer.class)指定序列化的类,该类继承JsonSerializer
@JsonDeserialize(using=SubscribePeriodDerializer.class)指定反序列化的类,该类继承JsonDeserializer

序列化

这样你可以按照你想要的方式序列化,name、value、index等都可以

public class SubscribePeriodSerializer extends JsonSerializer<SubscribePeriod>
{
	@Override
	public void serialize(ViewTime subscribePeriod, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException
	{
		jsonGenerator.writeNumber(ViewTime.getValue());
	}
}
反序列化
public class SubscribePeriodDerializer extends JsonDeserializer<SubscribePeriod>
{

	@Override
	public ViewTime deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
	{
		return ViewTime.fromValue(jp.getIntValue());
	}

}
这样你想怎么序列反序列都由你自定义,这里可能有人会问,不是要忽略注解该怎么办

这里你可以自定义一个类继承JacksonAnnotationIntrospector,譬如叫TestJackson

	@Override
	public boolean isHandled(Annotation ann)
	{
		if(ann.annotationType().equals(JsonSerialize.class)||ann.annotationType().equals(JsonDeserialize.class))
		{
			return super.isHandled(ann);
		}
		else
		{
			return false;
		}
	}

在这里重写该方法,这里我想使用序列化和反序列化的注解,其他注解全都忽略,那么我只需要在ann.annotationType().equals(JsonSerialize.class)||ann.annotationType().equals(JsonDeserialize.class)

的时候调用super.isHandled(ann);即可。

同时,在测试代码中

        ObjectMapper mapper = new ObjectMapper(new SmileFactory());
        //mapper.configure(SerializationConfig.Feature.USE_ANNOTATIONS, false);
        mapper.setAnnotationIntrospector(new TestJackson());
        System.out.println(mapper.writeValueAsBytes(ViewTime.FOREVER));
设置
mapper.setAnnotationIntrospector(new TestJackson());
注意标红的一定要注掉,或者改为true。如果没注掉的话,下面的 TestJackson 不起作用。还是会走NopAnnotationIntrospector的。

完...(留作备份)如有问题请指出,谢谢。
















  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值