FastJson解析对象后验签失败问题分析
背景
项目对接了一个第三方服务,对方返回的结果是json对象response,并且这个response中又嵌套了json对象bizPackage,http请求将返回数据转成了字符串格式,想要解析出来对象内部的签名sign和数据包bizPackage,用于验签,返回数据如下所示:
{"bizPackage":{"resultCode":"00000000","resultDesc":"成功","data":{"mid":"asfadsfadsfadsfads","photo":null},"biz":"fadsgfads","Seq":"596165165","Success":true,"gateway":200},"sign":"465465165165"}
问题
现场反馈服务响应验签失败,通过日志分析,发现如下两个问题:
1、通过FastJson将响应信息格式化之后,参数的顺序发生了变化,可以明显看到返回的响应字符串中参数顺序和格式化后的顺序发生变化
2、在解决了FastJson格式化顺序错乱的问题之后,又反馈了验签失败,通过日志查看,发现是格式化后将Json对象中value为null的key去除了,如下,当photo为null时,格式化去掉了这个key
解决方案
对于上面的两个问题,解决方案如下:
1、针对FastJson格式化数据之后,参数顺序发生变化,我们使用的是parseObject方法进行格式化,点进去格式化数据源码,发现parseObject方法是一个重载方法,有如下内容
public static JSONObject parseObject(String str) {if (str != null && !str.isEmpty()) {JSONReader.Context context = createReadContext(JSONFactory.getDefaultObjectReaderProvider(), DEFAULT_PARSER_FEATURE);JSONReader reader = JSONReader.of(str, context);try {Map<String, Object> map = new HashMap();reader.read(map, 0L);JSONObject jsonObject = new JSONObject(map);reader.handleResolveTasks(jsonObject);return jsonObject;} catch (JSONException var5) {Throwable cause = var5.getCause();if (cause == null) {cause = var5;}throw new com.alibaba.fastjson.JSONException(var5.getMessage(), (Throwable)cause);}} else {return null;}}public static JSONObject parseObject(String text, Feature... features) {if (text != null && !text.isEmpty()) {JSONReader.Context context = createReadContext(JSONFactory.getDefaultObjectReaderProvider(), DEFAULT_PARSER_FEATURE, features);JSONReader reader = JSONReader.of(text, context);String defaultDateFormat = DEFFAULT_DATE_FORMAT;if (!"yyyy-MM-dd HH:mm:ss".equals(defaultDateFormat)) {context.setDateFormat(defaultDateFormat);}boolean ordered = false;Feature[] var6 = features;int var7 = features.length;for(int var8 = 0; var8 < var7; ++var8) {Feature feature = var6[var8];if (feature == Feature.OrderedField) {ordered = true;break;}}try {Map<String, Object> map = ordered ? new LinkedHashMap() : new HashMap();reader.read((Map)map, 0L);JSONObject jsonObject = new JSONObject((Map)map);reader.handleResolveTasks(jsonObject);return jsonObject;} catch (JSONException var10) {Throwable cause = var10.getCause();if (cause == null) {cause = var10;}throw new com.alibaba.fastjson.JSONException(var10.getMessage(), (Throwable)cause);}} else {return null;}}
其中,有一段代码是判断是否使用LinkedHashMap的,我们知道这是一个有序的map,对其判断条件是ordered变量,再往上面找发现设置这个变量值的为feature == Feature.OrderedField
,因此,我们在格式化参数的时候,加上这个有序属性即可,例如JSONObject.parseObject(s, Feature.OrderedField)
,这样,我们格式化的对象内部顺序不会改变
2、对于格式化后去掉了值为null的key,这是FastJson的设计,顾名思义FastJson以快为目标,因此,对于值为null的数据,即使保留key,在获取key的时候依然返回null,那就没必要保留这个key了,这样而来获取key返回也是null,不存在歧义,对于这个问题,考虑过直接解析字符串结果,但是由于返回数据格式不固定,风险很大,这里我们选择添加JackSon处理,JackSon格式化数据既能保留顺序也能保留null值key,使用方式如下,添加相关依赖
<!-- Jackson --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.14.2</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.14.2</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.14.2</version></dependency>
下面是测试代码,验证了FastJson和JackSon对于格式化的改变
public static void main(String[] args) throws JsonProcessingException {String s = "{\"bizPackage\":{\"resultCode\":\"00000000\",\"resultDesc\":\"成功\",\"data\":{\"midFile\":\"asfadsfadsfadsfads\",\"photo\":null},\"bizSerial\":\"fadsgfads\",\"bizSeq\":\"596165165\",\"gatewaySuccess\":true,\"gateway\":200},\"sign\":\"465465165165\"}";log.info("请求服务,返回结果:"+s);ObjectMapper mapper = new ObjectMapper();JsonNode responseData = mapper.readTree(s);log.info("请求服务,格式一的:"+responseData);JSONObject jsonObject1 = JSONObject.parseObject(s);log.info("请求服务,格式二的:"+jsonObject1);JSONObject jsonObject = JSONObject.parseObject(s, Feature.OrderedField);log.info("请求服务,格式三的:"+jsonObject);String sign = responseData.get("sign").asText();log.info("请求服务,签名:"+sign);String bizPackage = responseData.get("bizPackage").toString();log.info("请求服务,格式二的:"+bizPackage);}
运行结果为
15:10:49.007 [main] INFO com.example.test.json.test - 请求服务,返回结果:{"bizPackage":{"resultCode":"00000000","resultDesc":"成功","data":{"midFile":"asfadsfadsfadsfads","photo":null},"bizSerial":"fadsgfads","bizSeq":"596165165","gatewaySuccess":true,"gateway":200},"sign":"465465165165"}
15:10:49.500 [main] INFO com.example.test.json.test - 请求服务,格式一的:{"bizPackage":{"resultCode":"00000000","resultDesc":"成功","data":{"midFile":"asfadsfadsfadsfads","photo":null},"bizSerial":"fadsgfads","bizSeq":"596165165","gatewaySuccess":true,"gateway":200},"sign":"465465165165"}
15:10:49.791 [main] INFO com.example.test.json.test - 请求服务,格式二的:{"sign":"465465165165","bizPackage":{"data":{"midFile":"asfadsfadsfadsfads"},"bizSeq":"596165165","gatewaySuccess":true,"resultCode":"00000000","bizSerial":"fadsgfads","resultDesc":"成功","gateway":200}}
15:10:49.792 [main] INFO com.example.test.json.test - 请求服务,格式三的:{"bizPackage":{"resultCode":"00000000","resultDesc":"成功","data":{"midFile":"asfadsfadsfadsfads"},"bizSerial":"fadsgfads","bizSeq":"596165165","gatewaySuccess":true,"gateway":200},"sign":"465465165165"}
15:10:49.796 [main] INFO com.example.test.json.test - 请求服务,签名:465465165165
15:10:49.799 [main] INFO com.example.test.json.test - 请求服务,格式二的:{"resultCode":"00000000","resultDesc":"成功","data":{"midFile":"asfadsfadsfadsfads","photo":null},"bizSerial":"fadsgfads","bizSeq":"596165165","gatewaySuccess":true,"gateway":200}