欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > fastjson:如何快速查找JSON Object中有没有指定名字的字段?

fastjson:如何快速查找JSON Object中有没有指定名字的字段?

2024/10/25 17:14:46 来源:https://blog.csdn.net/10km/article/details/140987230  浏览:    关键词:fastjson:如何快速查找JSON Object中有没有指定名字的字段?

需求

如本文标题,这是我最近遇到的一个问题,
如下是Spring服务收到的一个HTTP服务请求BODY中的JSON格式请求参数.
我需要知道这个请求中有没有名为tokenId的字段,如果有则获取该字段值,这决定着后续如何解析这个JSON.

{"deviceBean": {"_new": "0","modified": "000000","initialized": "7FFFFF","id": "22","groupId": "4","features": "0","name": "测试设备21","physicalAddress": "000000000015","addressType": "MAC","status": "ENABLE","network": "4G","model": "XIAKEDEV0","deviceDetail": "{\"device_name\":\"AN01\",\"made_date\":\"2022-01-02\",\"manufacturer\":\"NXP\"}","props": "{\"defineScreen\":{\"INCHSIZE0\":55,\"SCR0\":\"SCR0 1080 X 960 rectangle {0, 0, 1080, 960}\"},\"last_active_time\":\"2022-06-22 12:12:12\",\"disk_capacity\":\"1.2GB\"}","createTime": "2024-07-13 18:13:07","updateTime": "2024-07-13 18:13:07","onlineFlag": "-1","tokenId": "HELLO","rootGroup": "100"},"targetGroupId":32,"targetGroupName":"test group","array":["test group",false,2323,"tokenId"],"geoId":320,"tokenId": "TOKEN:bf4f202802020aa8"
}

显然,直接用JSON解析工具解析这个内容,获取 tokenId字段是最简单的方法,但是为了获取这个字段,不论是什么JSON解析工具都要解析完整个JSON数据才能得到.
但只为一个字段,就要解析全部数据,这在时间上这有些不划算呐.
你会问,为什么要执着的这点时间上的损失呢?
这个应用场景是在每个HTTP请求的拦截器HandlerInterceptor,那么如何能尽可能降低拦截器的耗时,对于整体系统响应性能是很重要的.
如果只是为了提取所关注的一个字段,就要对输入的JSON全部解析,这显然不是最有效率的办法.
上面这个例子中JSON数据并不长,还能接受,如果输入的JSON有长达几百KB时,这个时间损失就会非常明显.

解决方案

为了提取JSON Object中关注的字段,我尝试了各种办法:

  1. Antlr4 语法树(JSONVisitor)遍历解析测试

    Antlr4生成的解析器(Parser)对JSON完成解析生成语法树ParseTree,然后用JSONVisitor对语法树进行遍历,找到关注的字段.
    这个方案要先解析所有语法树再遍历语法树,性能与fastjson解析全部内容并没有明显提高(25%左右)

  2. Antlr4 词法分析器(Lexer)解析测试

    方案1的主要时间花在了解析并创建语法树过程,所以方案2基于antlr4,但不生成语法树(ParseTree),用Antlr4生成的词法解析器(Lexer)对JSON进行分析,对于Object类型的JSON,非关注的字段直接跳过.
    即遇到值对(Pair)[STRING ':' value],如果STRING不是匹配的字段名,直接跳到下一个值对(Pair), 遇要闭合的Token(LBRACE,LBRACKET,即'{','[')直接跳过,直到下结束Token(RBRACE,RBRACKET,即'}',']').

  3. fastjson词法分析器(Lexer)解析测试

    基于fastjson的词法解析器(JSONLexer)对Object类型的JSON进行分析,非关注的字段直接跳过.
    方案2的性能也不令人满意,究其原因是自娘胎里带的,也就是anntlr4生成的词法解析器因为要面向通用场景,逻辑太过复杂,造成效率不佳, 所以与方案2思路相同,只是采用fastjson的词法解析器(JSONLexer).

  4. fastjson 词法分析器(JSONScanner)字段名查找(seekObjectToField)测试

    基于fastjson,直接调用词法解析器JSONScanner.seekObjectToField(long fieldNameHash, boolean deepScan)方法获取关注字段的值.

经过反复性能测试比较,最终选择使用方案4,即 fastjson 的词法解析器(JSONScanner)提供的seekObjectToField(long fieldNameHash, boolean deepScan)方法来实现字段查找功能。比ANTLR 4生成的词法解析器快一个数量级。当然也比用fastjson完全解析JSON来得快.

因为都是基于fastjson的词法解析器(JSONLexer),所以方案3和方案4的性能相差不大

JSONScanner

com.alibaba.fastjson.parser.JSONScanner原本就提供了一个方法seekObjectToField(long fieldNameHash, boolean deepScan)查找JSON中指定的字段,
有了这个方法,前面的一切努力和折腾都白费了.用起来很简单:

import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.JSONLexer;
import com.alibaba.fastjson.parser.JSONScanner;
import com.alibaba.fastjson.parser.JSONToken;
import com.alibaba.fastjson.util.TypeUtils;public class FastjsonPairMatcher0 {/*** 查找JSON中key指定名称的字段,如果找到解析并返回字段值,否则返回{@code null},* 如果字段的值为'null'也同样返回{@code null}* @param src* @param key*/public static Object seekField(String src, String key) {String str = asInputString(src);@SuppressWarnings("resource")DefaultJSONParser parser = new DefaultJSONParser(src);JSONScanner lexerBase = (JSONScanner)parser.getLexer();/** 如果不是json object直接返回null */if (JSONToken.LBRACE == lexerBase.token()) {/*** 参照 com.alibaba.fastjson.JSONPath.PropertySegment 中* 对 JSONLexerBase.seekObjectToField方法的调用*/long propertyNameHash = TypeUtils.fnv1a_64(key);int matchStat = lexerBase.seekObjectToField(propertyNameHash, false);if (matchStat == JSONLexer.VALUE) {/** 如果有指定的则解析并返回 */return parser.parse();}}return null;}
}

具体数据请执行性能测试。

性能测试比较

-------------------------------------------------------T E S T S
-------------------------------------------------------
Running com.gitee.l0km.jsonvisitor.PerformanceTest
[main] (Timecost.java:36) ==fastjson 解析测试[基准]
[main] (Timecost.java:39) TIME COST: [79.0963ms]<<<首次执行时间
[main] (Timecost.java:39) TIME COST: [5.7771ms]<<<第二次执行时间
[main] (Timecost.java:41) TOTAL TIME COST: [164.3285ms]
[main] (Timecost.java:42) AVG TIME COST: [1.6433ms] for 100 times<<<100次测试平均值
[main] (Timecost.java:36) ==Antlr4 语法树(JSONVisitor)遍历解析测试
[main] (Timecost.java:39) TIME COST: [95.7972ms]<<<首次执行时间
[main] (Timecost.java:39) TIME COST: [29.3285ms]<<<第二次执行时间
[main] (Timecost.java:41) TOTAL TIME COST: [940.5354ms]
[main] (Timecost.java:42) AVG TIME COST: [9.4054ms] for 100 times<<<100次测试平均值
[main] (Timecost.java:36) ==Antlr4 词法分析器(Lexer)解析测试
[main] (Timecost.java:39) TIME COST: [29.8893ms]<<<首次执行时间
[main] (Timecost.java:39) TIME COST: [5.8947ms]<<<第二次执行时间
[main] (Timecost.java:41) TOTAL TIME COST: [653.2166ms]
[main] (Timecost.java:42) AVG TIME COST: [6.5322ms] for 100 times<<<100次测试平均值
[main] (Timecost.java:36) ==fastjson词法分析器(Lexer)解析测试
[main] (Timecost.java:39) TIME COST: [6.7722ms]<<<首次执行时间
[main] (Timecost.java:39) TIME COST: [3.6805ms]<<<第二次执行时间
[main] (Timecost.java:41) TOTAL TIME COST: [220.0746ms]
[main] (Timecost.java:42) AVG TIME COST: [2.2007ms] for 100 times<<<100次测试平均值
[main] (Timecost.java:36) ==fastjson 词法分析器(JSONScanner)字段名查找(seekObjectToField)测试
[main] (Timecost.java:39) TIME COST: [5.1285ms]<<<首次执行时间
[main] (Timecost.java:39) TIME COST: [2.7439ms]<<<第二次执行时间
[main] (Timecost.java:41) TOTAL TIME COST: [76.3522ms]
[main] (Timecost.java:42) AVG TIME COST: [0.7635ms] for 100 times<<<100次测试平均值
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.42 sec

不同的JSON数据得到的结果不一样,

上面这个测试数据中关注的tokenId字段在JSON的末尾,fastjson 词法分析器(JSONScanner)字段名查找(seekObjectToField)测试测试结果比fastjson解析[基准]快一倍。

如果关注的tokenId字段是JSON的第一个字段,fastjson 词法分析器(JSONScanner)字段名查找(seekObjectToField)测试测试结果比fastjson解析[基准]快两个数量级,因为它找到关注的字段后,就不需要再解析后面的数据。

总结

经此一役,深刻体会antlr4虽然好学好用,但其面向通用场景生成的JSON词法语法分析器,与手撸的针对场景优化过的JSON解析器在性能上差距还是挺大的.

完整代码

关于上面所有方案的实现代码参见码云仓库:

https://gitee.com/l0km/jsonvisitor

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com