json报文的序列化与反序列化问题总结(对比fastjson和jackson)
一、背景
有一个http查询接口,返回json报文,然后反序列为一个ApiResult对象,最后取出对象的data字段内容,并反序列为ClassroomCopyResultNotify对象。
但是,在最后反序列化的时候,报错如下:
java.lang.IllegalArgumentException: Cannot construct instance of `com.xxx.service.dto.ClassroomCopyResultNotify`
(although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value
('{"requestNo":"658ac2929d47479fbbe5a4a7ec86c451","newClassroomId":"FJGYYISL","sourceClassroomId":"XOJG0AOB"}')
at [Source: UNKNOWN; line: -1, column: -1]
二、伪代码
下面贴出相关的类代码:
1、ApiResult.java
@ApiModel
@Getter
public final class ApiResult<T> {
@ApiModelProperty(notes = "code")
private int code;
@ApiModelProperty(notes = "报文体")
private T data;
@ApiModelProperty(notes = "msg")
private String msg;
}
2、Json报文反序列化为ApiResult对象
- json报文:
{
"code": 200,
"msg": "success",
"data": "{\"requestNo\":\"658ac2929d47479fbbe5a4a7ec86c451\",\"newClassroomId\":\"FJGYYISL\",\"sourceClassroomId\":\"XOJG0AOB\"}"
}
- 反序列化,使用jackson框架的ObjectMapper
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
private static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
//忽略反序列化中的一些错误
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
ApiResult<?> apiResult = objectMapper.readValue(jsonStr, new TypeReference<ApiResult<?>>() {
});
3、取出apiResult的data内容,反序列化为java对象
下面的代码是出错时的写法。
ClassroomCopyResultNotify response = objectMapper.convertValue(apiResult.getData(), ClassroomCopyResultNotify.class);
错误在于,data字段的内容,并不是一个严格意义的json字符串。它多了一个反斜杠。
详细的报错内容见下:
java.lang.IllegalArgumentException: Cannot construct instance of `com.xxx.service.dto.ClassroomCopyResultNotify` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"requestNo":"658ac2929d47479fbbe5a4a7ec86c451","newClassroomId":"FJGYYISL","sourceClassroomId":"XOJG0AOB"}')
at [Source: UNKNOWN; line: -1, column: -1]
at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3922)
at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3853)
at java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
at sun.reflect.GeneratedMethodAccessor358.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.xxl.job.core.handler.impl.MethodJobHandler.execute(MethodJobHandler.java:29)
at com.xxl.job.core.thread.JobThread.run(JobThread.java:152)
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.xxx.service.dto.ClassroomCopyResultNotify` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"requestNo":"658ac2929d47479fbbe5a4a7ec86c451","newClassroomId":"FJGYYISL","sourceClassroomId":"XOJG0AOB"}')
at [Source: UNKNOWN; line: -1, column: -1]
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1429)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1059)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371)
at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:323)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1373)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:171)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161)
at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3917)
... 25 common frames omitted
- 期望data字段的内容见下:
{
"code": 200,
"msg": "success",
"data": {
"requestNo": "658ac2929d47479fbbe5a4a7ec86c451",
"newClassroomId": "FJGYYISL",
"sourceClassroomId": "XOJG0AOB"
}
}
注意: 因为Http响应报文在经过jackjson序列化的时候,data就是一个字符串,无法去掉反斜杠。
4、正确的写法应该是:
ClassroomCopyResultNotify response = JSON.parseObject(apiResult.getData().toString(), ClassroomCopyResultNotify.class);
三、完整的代码
- ClassroomCopyResultNotify.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ClassroomCopyResultNotify {
private String sourceClassroomId;
private String newClassroomId;
private String requestNo;
}
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xhtech.service.course.interfaces.http.assembler.ApiResult;
public class JsonUtils {
private static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
//忽略反序列化中的一些错误
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public static void main(String[] args) {
ApiResult<?> apiResult = getApiResult();
if (apiResult != null) {
try {
ClassroomCopyResultNotify response = JSON.parseObject(apiResult.getData().toString(), ClassroomCopyResultNotify.class);
System.out.println("json内容反序列化后(正确的写法):" +response);
} catch (Exception e) {
e.printStackTrace();
}
try {
ClassroomCopyResultNotify response = objectMapper.convertValue(apiResult.getData(), ClassroomCopyResultNotify.class);
System.out.println("json内容反序列化后(错误的写法):" +response);
} catch (Exception e) {
e.printStackTrace();
}
}
ApiResult<?> apiResultForExpected = getApiResultForExpected();
if (apiResultForExpected != null) {
try {
ClassroomCopyResultNotify response = JSON.parseObject(apiResult.getData().toString(), ClassroomCopyResultNotify.class);
System.out.println("期望的json内容反序列化后(fastjson的写法):" +response);
} catch (Exception e) {
e.printStackTrace();
}
try {
ClassroomCopyResultNotify response = objectMapper.convertValue(apiResultForExpected.getData(), ClassroomCopyResultNotify.class);
System.out.println("期望的json内容反序列化后(jackson的写法):" + response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static ApiResult<?> getApiResult() {
try {
String jsonStr = "{\n" +
" \"code\": 200,\n" +
" \"msg\": \"success\",\n" +
" \"data\": \"{\\\"requestNo\\\":\\\"658ac2929d47479fbbe5a4a7ec86c451\\\",\\\"newClassroomId\\\":\\\"FJGYYISL\\\",\\\"sourceClassroomId\\\":\\\"XOJG0AOB\\\"}\"\n" +
"}";
ApiResult<?> apiResult = objectMapper.readValue(jsonStr, new TypeReference<ApiResult<?>>() {
});
return apiResult;
} catch (Exception e) {
return null;
}
}
private static ApiResult<?> getApiResultForExpected() {
try {
String expectedJsonStr = "{\n" +
" \"data\": {\n" +
" \"requestNo\": \"658ac2929d47479fbbe5a4a7ec86c451\",\n" +
" \"newClassroomId\": \"FJGYYISL\",\n" +
" \"sourceClassroomId\": \"XOJG0AOB\"\n" +
" },\n" +
" \"code\": 200,\n" +
" \"msg\": \"success\"\n" +
"}";
ApiResult<?> apiResult = objectMapper.readValue(expectedJsonStr, new TypeReference<ApiResult<?>>() {
});
return apiResult;
} catch (Exception e) {
return null;
}
}
}
- 运行结果
json内容反序列化后(正确的写法):ClassroomCopyResultNotify(sourceClassroomId=XOJG0AOB, newClassroomId=FJGYYISL, requestNo=658ac2929d47479fbbe5a4a7ec86c451)
java.lang.IllegalArgumentException: Cannot construct instance of `com.xxx.service.dto.ClassroomCopyResultNotify` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"requestNo":"658ac2929d47479fbbe5a4a7ec86c451","newClassroomId":"FJGYYISL","sourceClassroomId":"XOJG0AOB"}')
at [Source: UNKNOWN; line: -1, column: -1]
at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3922)
at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3853)
at JsonUtils.main(JsonUtils.java:31)
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.xxx.service.dto.ClassroomCopyResultNotify` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"requestNo":"658ac2929d47479fbbe5a4a7ec86c451","newClassroomId":"FJGYYISL","sourceClassroomId":"XOJG0AOB"}')
at [Source: UNKNOWN; line: -1, column: -1]
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1429)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1059)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371)
at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:323)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1373)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:171)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161)
at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3917)
... 2 more
期望的json内容反序列化后(fastjson的写法):ClassroomCopyResultNotify(sourceClassroomId=XOJG0AOB, newClassroomId=FJGYYISL, requestNo=658ac2929d47479fbbe5a4a7ec86c451)
期望的json内容反序列化后(jackson的写法):ClassroomCopyResultNotify(sourceClassroomId=XOJG0AOB, newClassroomId=FJGYYISL, requestNo=658ac2929d47479fbbe5a4a7ec86c451)
四、总结
通过上面代码的运行结果可以得知,fastjson在处理带反斜杠的json字符串时,兼容性比jackson要强。