FastJson中的JsonPath还挺好用的

FastJson中的JsonPath还挺好用的

我们的SRM系统和ERP系统需要对接很多供应商,以前的业务都是走线下做,那种效率极低。后来就都转为线上,直接调用供应商接口,但是这样做又会有一个问题,就是那么多供应商,每家供应商的接口和字段都不一样,报文结构也会不一样。难不成,我们程序里面要真多每家供应商都写一套逻辑吗?这要是做到业务系统的话,那工作量也确实不小。

后来,我们想了办法,就是加了一个转发应用,不管是内部业务系统调供应商接口,还是供应商调我们内部业务系统,都走这个转发应用,在转发应用中来做每个供应商的接口对接和报文转换,业务系统只需要传接口编号即可,不需要再改业务系统中的代码。

通过分析,每家供应商是鉴权方式是不一样的,但是请求方式基本都是POST,然后报文是JSON放在BODY中。那么这样一来,就是针对每家供应商实现一套登录鉴权,报文基本可以表达式动态转换。这时候,我想到了Jayway JsonPath

本来想从Jayway JsonPath的源代码中借鉴一下思路,但是看了下代码,算了,还是直接用吧 .

后来又一想,其实FastJson在1.2版本开始也有JsonPath,从需求来看,它也能够满足,那就直接用 FastJson JsonPath吧。

先来看看基本的语法和功能

1. 支持语法

JSONPATH 描述
$ 根对象,例如$.name
[num] 数组访问,其中num是数字,可以是负数。例如$[0].leader.departments[-1].name
[num0,num1,num2…] 数组多个元素访问,其中num是数字,可以是负数,返回数组中的多个元素。例如$[0,3,-2,5]
[start:end] 数组范围访问,其中start和end是开始小表和结束下标,可以是负数,返回数组中的多个元素。例如$[0:5]
[start:end :step] 数组范围访问,其中start和end是开始小表和结束下标,可以是负数;step是步长,返回数组中的多个元素。例如$[0:5:2]
[?(@.key)] 对象属性非空过滤,例如$.departs[?(@.name)]
[?(@.key > 123)] 数值类型对象属性比较过滤,例如$.departs[id >= 123],比较操作符支持=,!=,>,>=,<,<=
[?(@.key = ‘123’)] 字符串类型对象属性比较过滤,例如$.departs[?(@..name = ‘123’)],比较操作符支持=,!=,>,>=,<,<=
[?(@.key like ‘aa%’)] 字符串类型like过滤, 例如$.departs[?(@..name like ‘sz*’)],通配符只支持%支持not like
[?(@.key rlike ‘regexpr’)] 字符串类型正则匹配过滤,
[?(@.key in (‘v0’, ‘v1’))] IN过滤, 支持字符串和数值类型 例如: $.departs[?(@.name in (‘wenshao’,’Yako’))] $.departs[id not in (101,102)]
[?(@.key between 234 and 456)] BETWEEN过滤, 支持数值类型,支持not between 例如: $.departs[?(@.id between 101 and 201)]
$.departs[?(@.id not between 101 and 201)]
length() 或者 size() 数组长度。例如$.values.size() 支持类型java.util.Map和java.util.Collection和数组
. 属性访问,例如$.name
.. deepScan属性访问,例如$..name
* 对象的所有属性,例如$.leader.*
[‘key’] 属性访问。例如$[‘name’]
[‘key0’,’key1’] 多个属性访问。例如$[‘id’,’name’]

以下两种写法的语义是相同的:

1
$.store.book[0].title

1
$['store']['book'][0]['title']

1.1 函数

Function 返回类型 Description
type string 返回对象的类型
length/size integer 返回集合或者字符串的长度
first Any 集合中第一个元素
last Any 集合中最后一个元素
values sequence Map类型的Values
entries sequence Map类型的EntrySet
trim string 对字符串做trim后返回
double double 将目标类型转换为double类型
ceil number 对数值类型做ceil处理返回
abs number 返回对数值类型的绝对值
lower string 将字符串转换小写
upper string 将字符串转换成大写
index(x) int 其中参数x支持数值和字符串

1.2 聚合函数

Function 返回类型 Description
min
max
first 返回集合中的第一个元素
last 返回集合中的最后一个个元素
avg double

1.3 Filter Operators

Operator Description
= 相等
!= or <> 不等
> 大于
>= 大于等于
< 小于
<= 小于等于
~=
like 类似SQL中LIKE语法
not like
rlike
not rlike
in
not in
between
not between
starts_with
not starts_with
ends_with
not ends_with
contains
not contains

2. 语法举例

JSONPath 语义
$ 根对象
$[-1] 最后元素
$[:-2] 第1个至倒数第2个
$[1:] 第2个之后所有元素
$[1,2,3] 集合中1,2,3个元素

接下来就直接干吧。看看我们这个简陋的字段映射界面

在界面上,我们可以设置要转换的字段值类型,日期格式还有简单的字典值转换以及简单的表达式(多个字段合并或者是字段值判断),我们需要取出字段的映射关系,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = BsFileds.class)
public class BsFiledsVo implements Serializable {

@Serial
private static final long serialVersionUID = 1L;

/**
*
*/
@ExcelProperty(value = "")
private Long filedId;

/**
* 字段名称
*/
@ExcelProperty(value = "字段名称")
private String twName;

/**
* 字段类型
*/
@ExcelProperty(value = "字段类型")
private String twType1;

/**
* 值类型
*/
@ExcelProperty(value = "值类型")
private String twType2;

/**
* 字段说明
*/
@ExcelProperty(value = "字段说明")
private String twRemark;

/**
* 字段名称
*/
@ExcelProperty(value = "字段名称")
private String supName;

/**
* 字段类型
*/
@ExcelProperty(value = "字段类型")
private String supType1;

/**
* 值类型
*/
@ExcelProperty(value = "值类型")
private String supType2;

/**
* 字段说明
*/
@ExcelProperty(value = "字段说明")
private String supRemark;

/**
*
*/
@ExcelProperty(value = "")
private Long interfaceId;
private Long interSupId;

//通威字段层级
private String expression1;
//供应商字段层级
private String expression2;
//字段补充说明
private String restock1;
//供应商字段补充说明
private String restock2;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void setValue(BsFiledsVo bsFiledsVo, JSONObject result, Object eval, List<SupDictVo> supFiledDictVos) {
if (StringUtils.isNotEmpty(bsFiledsVo.getSupType2())) {
switch (bsFiledsVo.getSupType2()) {
case "BigDecimal":
JSONPath.set(result, bsFiledsVo.getExpression2(), this.convertToBigDecimal(String.valueOf(eval)));
break;
case "Date":
JSONPath.set(result, bsFiledsVo.getExpression2(), this.convertToDateStr(String.valueOf(eval), bsFiledsVo.getRestock2()));
break;
default:
JSONPath.set(result, bsFiledsVo.getExpression2(), this.convertToDictValue(String.valueOf(eval), supFiledDictVos));
break;
}
}
}


public JSONObject convertDynamicJsonMapper(JSONObject source) {
String interfaceCode = PwsUtils.getInterfaceCode(source);
if (StringUtils.isEmpty(interfaceCode)) {
return new JSONObject();
}
List<BsFiledsVo> bsFiledsVos = filedsMapper.selectDynamicFileds(interfaceCode);
//如果没有映射关系,则原样返回
if (CollectionUtil.isEmpty(bsFiledsVos)) {
return source;
}
List<SupDictVo> supDictVos = filedsMapper.selectDynamicDict(interfaceCode);
Map<String, List<SupDictVo>> stringListMap = new HashMap<>();
if (CollectionUtil.isNotEmpty(supDictVos)) {
stringListMap = supDictVos.stream().collect(Collectors.groupingBy(SupDictVo::getEFieldCode));
}
JSONObject result = new JSONObject();
JSONObject data = source.getJSONObject("data");
for (BsFiledsVo bsFiledsVo : bsFiledsVos) {
if (StringUtils.isEmpty(bsFiledsVo.getExpression2())) {
continue;
}
Object eval = JSONPath.eval(data, bsFiledsVo.getExpression1());
setValue(bsFiledsVo, result, eval, supFiledDictVos,false);
}
return result;
}

这里我原始字段取值表达式$.RECEIVE_INFO_LIST[0].TALK_DESC,目标表达式$.statementDetails[0].commodityDetails[0].businessNegotiateReason

原始实例报文

1
2
3
4
5
{
"RECEIVE_INFO_LIST": [{
"TALK_DESC": "test"
}]
}

转换后的报文

1
2
3
4
5
6
7
{
"statementDetails": [{
"commodityDetails": [{
"businessNegotiateReason": "test"
}]
}]
}

目前基本的字段映射,格式转换能满足了,遇到特殊情况,咱们在特殊处理吧。我们还给这个应用取了名字,叫 供应商直连网关

FastJson中的JsonPath还挺好用的

https://blogs.52fx.biz/posts/2139301912.html

作者

eyiadmin

发布于

2024-12-26

更新于

2024-12-26

许可协议

评论