对于一个java类Student, 其定义如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String name;
private int age;
private Address address;
private YearMonth birthday;
private LocalDate date;
}
在使用fastjson反序列化该对象时会抛出一个异常,
JSON parse error: default constructor not found. class java.time.YearMonth; nested exception is com.alibaba.fastjson.JSONException: default constructor not found. class java.time.YearMonth, 通过报错日志可知由于没有找到java.time.YearMonth的默认构造函数,所以fastjson在反序列化时会抛出这样一个异常。
fastjson和默认构造函数
为什么需要默认构造函数
fastjson在反序列化中利用对象的默认构造函数反射生成Java对象,如果没有找到公共的无参构造函数会抛出上述的异常。
public Object createInstance(DefaultJSONParser parser, Type type) {
......
if (beanInfo.defaultConstructor == null && beanInfo.factoryMethod == null) {
return null;
}
if (beanInfo.factoryMethod != null && beanInfo.defaultConstructorParameterSize > 0) {
return null;
}
Object object;
try {
Constructor<?> constructor = beanInfo.defaultConstructor;
if (beanInfo.defaultConstructorParameterSize == 0) {
if (constructor != null) {
object = constructor.newInstance();
} else {
object = beanInfo.factoryMethod.invoke(null);
}
} else {
.........
}
} catch (JSONException e) {
throw e;
} catch (Exception e) {
throw new JSONException("create instance error, class " + clazz.getName(), e);
}
........
return object;
}
两种解决办法
最简单的方式就是给对象添加一个无参构造函数,可是对于java.time.YearMonth·这样jdk提供的类而言这种方法就行不通了。java.time·是Java8增加的事件处理库,其最大的特性是线程安全,该包下面的类都没有公开的构造函数,java.time.YearMonth·的私有构造函数如下。
/**
* Constructor.
*
* @param year the year to represent, validated from MIN_YEAR to MAX_YEAR
* @param month the month-of-year to represent, validated from 1 (January) to 12 (December)
*/
private YearMonth(int year, int month) {
this.year = year;
this.month = month;
}
对于这种无法增加默认构造函数的类可以采用自定义序列化的方式。
fastjson自定义序列化
fastjson提供了ObjectDeserializer 和ObjectSerializer 两个接口,用户只要实现这两个接口的抽象方法就可以根据业务需要自定义实现自己的反序列化和序列化逻辑。在fastjson中默认已经提供了常见类的一些序列化/反序列化方式,例如:
SimpleDateFormat对应的MiscCodec,java.sql.Time对应的TimeDeserializer,Map及其实现类对应的MapDeserializer等等,另外还有一个用于Java8时间类对应的Jdk8DateCodec,可是比较可惜的是Jdk8DateCodec至支持了
"java.time.LocalDateTime",
"java.time.LocalDate","
java.time.LocalTime",
"java.time.ZonedDateTime","
java.time.OffsetDateTime",
"java.time.OffsetTime",
"java.time.ZoneOffset",
"java.time.ZoneRegion",
"java.time.ZoneId",
"java.time.Period",
"java.time.Duration","
java.time.Instant"
这12个类对于该包下面的YearMonth, MonthDay等类并未提供支持。我们可以参考Jdk8DateCodec的实现提供对YearMonth的支持。具体实现如下:
public class YearMonthCodec implements ObjectSerializer, ObjectDeserializer {
@Override
public YearMonth deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
JSONLexer lexer = parser.lexer;
if (lexer.token() == JSONToken.NULL){
lexer.nextToken();
return null;
}
if (lexer.token() == JSONToken.LITERAL_STRING) {
String text = lexer.stringVal();
lexer.nextToken();
if ("".equals(text)) {
return null;
}
if (type == YearMonth.class) {
return YearMonth.parse(text);
}
}
return null;
}
@Override
public int getFastMatchToken() {
return 0;
}
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
SerializeWriter out = serializer.out;
if (object == null) {
out.writeNull();
} else {
out.writeString(object.toString());
}
}
在使用前还需要将其注册到ParserConfig和SerializeConfig配置中,这样fastjson会根据我们定义的逻辑进行序列化和反序列化操作。
ParserConfig.getGlobalInstance().putDeserializer(YearMonth.class, new YearMonthCodec());
SerializeConfig.getGlobalInstance().put(YearMonth.class, new YearMonthCodec());
除了使用fastjson全局配置,也可以使用@JSONField注解修饰相应的字段:
@JSONFiled(serializeUsing = YearMonthCodec.class, deserializeUsing = YearMonthCodec.class)
private YearMonth birthday;
相比较而言,使用fastjson全局配置的方式应该是更加方便一点的。