最近在使用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的。
完...(留作备份)如有问题请指出,谢谢。