FastJson $href循环引用解决方案
- 问题背景
由于项目中需要对微服务中Swagger-ui中生成的API json 内容解析,所以准备采用FastJson
组件进行解析Json内容,在解析的过程中出现$ref循环引用和重复引用问题。
- 需要解析的
Json
内容
{
"swagger":"2.0",
"info":{"description":"API","version":"2.0","title":"Api"},
"host":"10.10.4.66:19021",
"basePath":"/",
"tags":[
{"name":"demo-robot-api","description":"Demo Robot Api"},
{"name":"echo-api","description":"Echo Api"},
{"name":"hystrix-api","description":"Hystrix Api"},
{"name":"value-api","description":"Value Api"}
],
"paths":{
"/demo/echo/echoObjectByObject":{
"post":{
"tags":["echo-api"],
"summary":"传入对象,返回对象",
"operationId":"echoObjectByObjectUsingPOST",
"consumes":["application/json"],
"produces":["*/*"],
"parameters":[
{
"name":"time",
"in":"query",
"required":false,
"type":"string",
"format":"date-time"
},
{
"name":"value",
"in":"query",
"required":false,
"type":"string"
}
],
"responses":{
"200":{
"description":"OK",
"schema":{"$ref":"#/definitions/ApiResponse«EchoVo»"}
}
},
"deprecated":false
}
},
"post":{
"tags":["demo-robot-api"],
"summary":"更新子对象",
"operationId":"updateJobUsingPOST",
"consumes":["application/json"],
"produces":["*/*"],
"parameters":[
{
"in":"body",
"name":"vo",
"description":"vo",
"required":true,
"schema":{"$ref":"#/definitions/DemoRobotJobVo"}
}
],
"responses":{
"200":{
"description":"OK",
"schema":{"$ref":"#/definitions/ApiResponse«string»"}
}
},
"deprecated":false
}
},
"/robot/demo/updateMain":{
"post":{
"tags":["demo-robot-api"],
"summary":"更新主对象",
"operationId":"updateMainUsingPOST",
"consumes":["application/json"],
"produces":["*/*"],
"parameters":[
{
"in":"body",
"name":"vo",
"description":"vo",
"required":true,
"schema":{"$ref":"#/definitions/DemoRobotMainVo"}
}
],
"responses":{
"200":{
"description":"OK",
"schema":{"$ref":"#/definitions/ApiResponse«string»"}
}
},
"deprecated":false
}
}
},
"definitions":{
"ApiResponse«DemoRobotJobVo»":{
"type":"object",
"properties":{
"data":{"type":"object"},
"status":{"type":"integer","format":"int64"},
"statusText":{"type":"string"}
},
"title":"ApiResponse«DemoRobotJobVo»"
},
"ApiResponse«DemoRobotMainVo»":{
"type":"object",
"properties":{
"data":{"type":"object"},
"status":{"type":"integer","format":"int64"},
"statusText":{"type":"string"}
},
"title":"ApiResponse«DemoRobotMainVo»"
},
"ApiResponse«EchoVo»":{
"type":"object",
"properties":{
"data":{"type":"object"},
"status":{"type":"integer","format":"int64"},
"statusText":{"type":"string"}
},
"title":"ApiResponse«EchoVo»"
},
"ApiResponse«ResultPage«DemoRobotJobVo»»":{
"type":"object",
"properties":{
"data":{"type":"object"},
"status":{"type":"integer","format":"int64"},
"statusText":{"type":"string"}
},
"title":"ApiResponse«ResultPage«DemoRobotJobVo»»"
},
"ApiResponse«ResultPage«DemoRobotMainVo»»":{
"type":"object",
"properties":{
"data":{"type":"object"},
"status":{"type":"integer","format":"int64"},
"statusText":{"type":"string"}
},
"title":"ApiResponse«ResultPage«DemoRobotMainVo»»"
},
"ApiResponse«ResultPage«EchoVo»»":{
"type":"object",
"properties":{
"data":{"type":"object"},
"status":{"type":"integer","format":"int64"},
"statusText":{"type":"string"}
},
"title":"ApiResponse«ResultPage«EchoVo»»"
},
"ApiResponse«string»":{
"type":"object",
"properties":{
"data":{"type":"object"},
"status":{"type":"integer","format":"int64"},
"statusText":{"type":"string"}
},
"title":"ApiResponse«string»"
},
"DemoRobotJobVo":{
"type":"object",
"properties":{
"comCode":{"type":"string"},
"consumeEnergy":{"type":"number"},
"endTime":{"type":"string","format":"date-time"},
"id":{"type":"integer","format":"int64"},
"insertTimeForHis":{"type":"string","format":"date-time"},
"jobContent":{"type":"string"},
"jobImage":{"type":"string","format":"byte"},
"operateTimeForHis":{"type":"string","format":"date-time"},
"robotId":{"type":"integer","format":"int64"},
"startTime":{"type":"string","format":"date-time"},
"version":{"type":"integer","format":"int32"},
"walkCount":{"type":"integer","format":"int64"}
},
"title":"DemoRobotJobVo"
},
"DemoRobotMainVo":{
"type":"object",
"properties":{
"comCode":{"type":"string"},
"id":{"type":"integer","format":"int64"},
"insertTimeForHis":{"type":"string","format":"date-time"},
"jobList":{
"type":"array",
"items":{"$ref":"#/definitions/DemoRobotJobVo"}
},
"manufactureDate":{"type":"string","format":"date-time"},
"manufactureName":{"type":"string"},
"nickname":{"type":"string"},
"operateTimeForHis":{"type":"string","format":"date-time"},
"rechargeCount":{"type":"integer","format":"int32"},
"robotHeight":{"type":"number"},
"robotSn":{"type":"string"},
"version":{"type":"integer","format":"int32"}
},
"title":"DemoRobotMainVo"
},
"EchoVo":{
"type":"object",
"properties":{
"time":{"type":"string","format":"date-time"},
"value":{"type":"string"}
},
"title":"EchoVo"
},
"ResultPage«DemoRobotJobVo»":{
"type":"object",
"properties":{
"data":{"type":"array","items":{"$ref":"#/definitions/DemoRobotJobVo"}},
"pageNo":{"type":"integer","format":"int64"},
"perPage":{"type":"integer","format":"int64"},
"totalCount":{"type":"integer","format":"int64"}
},
"title":"ResultPage«DemoRobotJobVo»"
},
"ResultPage«DemoRobotMainVo»":{
"type":"object",
"properties":{
"data":{
"type":"array",
"items":{"$ref":"#/definitions/DemoRobotMainVo"}
},
"pageNo":{
"type":"integer","format":"int64"},
"perPage":{"type":"integer","format":"int64"},
"totalCount":{"type":"integer","format":"int64"}
},
"title":"ResultPage«DemoRobotMainVo»"
},
"ResultPage«EchoVo»":{
"type":"object",
"properties":{
"data":{
"type":"array",
"items":{"$ref":"#/definitions/EchoVo"}
},
"pageNo":{
"type":"integer",
"format":"int64"
},
"perPage":{
"type":"integer",
"format":"int64"
},
"totalCount":{
"type":"integer",
"format":"int64"
}
},
"title":"ResultPage«EchoVo»"
}
}
}
- 阿里Json解析组件
FastJson
,
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
- Json字符串解析方法
public void parseJson(String json) {
JSONObject jsonObject = JSONObject.parseObject(json);
JSONObject apiPaths = (JSONObject)jsonObject.get("paths");
Set<String> keySet = apiPaths.keySet();
for(String key : keySet) {
JSONObject apiPath = (JSONObject)apiPaths.get(key);
Set<String> methodType = apiPath.keySet();
for(String type : methodType) {
System.out.println("type: "+type);
JSONObject jsonApi = (JSONObject) apiPath.get(type);
API api = JSONObject.parseObject(jsonApi.toJSONString(), API.class);
System.out.println(api);
}
}
}
API json对象vo类
public class API {
public String summary;
public String deprecated;
public String produces;
public String operationId;
public String tags;
public JSONArray parameters;
public JSON responses;
public String consumes;
//setter/getter 省略
}
- 解析结果
type: get
API [summary=插入Test, deprecated=false, produces=["*/*"],
operationId=insertTestDataUsingGET, tags=["demo-robot-api"],
parameters=[{"in":"path","name":"jobCount","format":"int32",
"description":"jobCount","type":"integer","required":true},
{"in":"path","name":"mainCount","format":"int32",
"description":"mainCount","type":"integer","required":true}],
responses={"200":{"schema":{"$ref":"@"},"description":"OK"}}, consumes=null]
type: post
API [summary=根据生产厂家查找对象(包含可能的子对象),
deprecated=false, produces=["*/*"],
operationId=selectByManufactureNameUsingPOST,
tags=["demo-robot-api"],
parameters[{"in":"query","name":"manufactureName",
"description":"manufactureName","type":"string","required":true}],
responses={"200":{"schema":{"$ref":"@"},"description":"OK"}},
consumes=["application/json"]]
...
....
- 问题描述
上述中对于response中的对象引用全部被解析为 “@”
- 问题原因
解析为@是由于对象存在着循环引用或重复引用,即同一个对象在多个地方都
有引用,所以导致解析出错
- 解决方案
1、可以通过关闭全局的默认循环引用检测
JSON.DEFAULT_PARSER_FEATURE = Feature.DisableCircularReferenceDetect.getMask();
2、可以在需要解析的地方,指定关闭循环引用检测
JSONObject jsonObject = JSONObject.parseObject(json,
Feature.DisableCircularReferenceDetect);
- FastJson 关闭循环引用
注意
1、需要注意FastJson的版本,该方案是基于1.2.58,在版本1.2.4中则会
解析失败,出现空指针异常,建议使用高版本
2、在关闭循环引用检测的时候,需要注意采用的为 Feature 的枚举类型,而
不是SerializerFeature中的枚举类型
- 最终解析代码为
public void parseJson(String json) {
JSON.DEFAULT_PARSER_FEATURE = Feature.DisableCircularReferenceDetect.getMask();
JSONObject jsonObject = JSONObject.parseObject(json);
JSONObject apiPaths = (JSONObject)jsonObject.get("paths");
Set<String> keySet = apiPaths.keySet();
for(String key : keySet) {
JSONObject apiPath = (JSONObject)apiPaths.get(key);
Set<String> methodType = apiPath.keySet();
for(String type : methodType) {
System.out.println("type: "+type);
JSONObject jsonApi = (JSONObject) apiPath.get(type);
API api = JSONObject.parseObject(jsonApi.toJSONString(), API.class);
System.out.println(api);
}
}
}
- 解析的结果为
type: get
API [summary=home, deprecated=false, produces=["*/*"],
operationId=homeUsingGET, tags=["hystrix-api"],
parameters=[{"in":"query","name":"name","description":"name",
"type":"string","required":false}],
responses={"200":{"schema":{"$ref":"#/definitions/ApiResponse«string»"},
"description":"OK"}}, consumes=null]
由此可知,上述结果中response的 $href
为对应的引用对象