java json parser_自己实现JSON解析器 JsonParser
自己实现json parser,只有一个类,不依赖任何第三方工具。
背景
为什么要实现json解析器呢?在我实现一个rpc框架的过程中,注册中心部分使用consul,而consul的api是通过restful http api来提供的,数据交互格式为json,此时就需要用到json解析工具。
让我们回顾一下java界较为通用的json处理库,常用的json处理库有jackson,gson,fastjson,其他还有许多json工具包,不过都不流行或已退出历史舞台。
java ee也有一个相关的jsr,jsr 353 json processing api,定义了通用的json 处理 api,一个参考实现是oracle 的glassfish jsonp。
jackson是最完善的json处理工具,实现的功能最多,并且支持jaxb注解,而且也有支持适配 jsr353 json processing api的模块。。也是我个人最喜欢的json处理工具包。其实fastjson的很多实现的常量定义都能看到jaxkson的影子。
本来决定使用jackson的,一般来讲现在spring是企业应用的标配,而spring mvc应用通常都会依赖jackson的包。因此依赖一下jackson的包也可以接受。但后来又考虑了几次,觉得依赖第三方的包毕竟不美,额外带来依赖总是会增加复杂性,对于基础组件,除了日志门面框架这样与实现无关的包,还是尽量少依赖其他库为妙。
因此决定自己写一个json解析工具,考虑了一下觉得也不是十分复杂,用递归的方式解析json串即可。具体实现思路接下来分析。
实现思路
json的结构分析
json的结构包含几种元素:
object(name value pair object),此处指狭义的对象,名值对形式。广义上任何元素都是对象。
array,由[]符号包裹,元素用英文逗号分隔。
字面类型,字面类型最为简单,不能再嵌套
number
boolean,true or false
null
下面放几张json.org的图,以直观的形式展示json格式。
json object
json object
json object
object 内部的value和array内部的元素都可以是任意组成类型,可以存在任意层次的嵌套。因此用递归方式解析比较简单。
实现思路
a) 基本概念
trim ,trim是把一个字符序列的头尾的不可见字符去掉,由于json允许在元素之间存在任意个tab、换行、空格。因此可能有许多地方需要用到trim。
b) processObjcet: object解析
以"{"开始,正确找到对应的结束的"}",由于花括号可能存在多重嵌套,找到正确的结束符号是有技巧的。记下整个{}区块的位置。
脱去头尾的{},中间的部分是properties列表,以name:value,...的形式存储。解析properties列表。
标记nameStartMark,初始为0,遇到冒号":",从nameStartMark到冒号前都为nameToken(需要trim),从冒号后开始寻找nextValue。同样需要注意(1.)提及的花括号和中括号匹配,遇到逗号或结束表示value区块结束。(由findNextValue函数完成)
移动游标到找到的value区块后,并更新nameStartMark标记。
循环执行(3.)和(4.),直到不再有冒号。
注意:找到的value区块移交给另一个函数processValue处理,此处存在递归。
c) processArray: array解析
与object类似,但是比object简单
以"["开始,正确找到对应的结束的"]",由于方括号可能存在多重嵌套,找到正确的结束符号是有技巧的。记下整个[]区块的位置。
脱去头尾的[],中间的部分是elements列表,以element1,element2...的形式存储。
直接循环执行findNextValue即可,直到结束
注意:同上,找到的value区块移交给另一个函数processValue处理,此处存在递归。
d) processValue: value解析
此处是一个递归操作,value本身可能是一个字面量,或者是object,或者是array。
如果value区块以"{"开头,则是object,移交给processObjcet 做object解析,递归操作。
如果value区块以"["开头,则是array,移交给processArray 做array解析,递归操作。
字面量,如string,boolean,number,null直接解析。string可能有转义字符,这个目前没有考虑处理。
e) completeSymbolPair: 寻找匹配的{}和[]
由于{}和[]都可能存在多重嵌套,因此需要正确的找到一个开始的花括号对应的结束符号,方括号同理。
这个可以用这个原理:符号一定是成对出现的。
步骤如下:
对于已知的第一个左符号,定义symbolsScore=1,index=1,
遍历后续的字符,遇到左符号则symbolsScore++,遇到右符号则symbolsScore--。
直到symbolsScore==0,则找到正确的结束符。
左边开始符号到右边结束符号之间的内容就是需要的内容。
代码实现
方法原型
public class JsonParser {
private final String json;
/**
* 入口方法
* @return 解析完成的对象
*/
public Object parse() {
CharsRange trimmedJson = newRange(0, json.length()).trim();
return processValue(trimmedJson);
}
private Object processPlainObject(CharsRange range) {}
private List processProperties(CharsRange range) {}
private List> processArray(CharsRange range) {}
/**
* @param chars
* @return value segment trimmed.
*/
private CharsRange findNextValue(CharsRange chars, AtomicInteger readCursor) {}
private CharsRange completeSymbolPair(CharsRange trimChars, AtomicInteger readCursor, String symbolPair) {}
private Object processValue(CharsRange valueSegment) {}
static class Property { final String name, value;}
class CharsRange { final int start, end;}
}
具体代码
此处当然要放具体的代码,只有一个类。
package io.destinyshine.storks.utils.json;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.extern.slf4j.Slf4j;
/**
* @author liujianyu
* @date 2017/10/17
*/
@Slf4j
public class JsonParser {
private final String json;
public JsonParser(String json) {
this.json = json;
}
/**
* 入口方法
* @return 解析完成的对象
*/
public Object parse() {
CharsRange trimmedJson = newRange(0, json.length()).trim();
return processValue(trimmedJson);
}
private Object processPlainObject(CharsRange range) {
List properties = processProperties(newRange(range.start + 1, range.end - 1));
Map object = new HashMap<>();
properties.forEach(prop -> object.put(prop.name, prop.value));
return object;
}
private List processProperties(CharsRange range) {
List properties = new ArrayList<>();
int nameStartMark = range.start;
for (int i = range.start; i < range.end; i++) {
char ch = json.charAt(i);
if (ch == ':') {
CharsRange nameToken = newRange(nameStartMark, i).trim();
AtomicInteger readCursor = new AtomicInteger();
CharsRange valueSegment = findNextValue(newRange(++i, range.end), readCursor);
i = readCursor.intValue() + 1;
nameStartMark = i;
logger.info("nameToken:{},\nvalueSegment:{}", nameToken, valueSegment);
//TODO::valid nameToken is start and end with '"'
final String name = newRange(nameToken.start + 1, nameToken.end - 1).toString();
final Object value = processValue(valueSegment);
properties.add(Property.of(name, value));
}
}
return properties;
}
private List> processArray(CharsRange range) {
return processElements(newRange(range.start + 1, range.end - 1));
}
private List> processElements(CharsRange range) {
List array = new ArrayList<>();
int elementStartMark = range.start;
for (int i = range.start; i < range.end; i++) {
AtomicInteger readCursor = new AtomicInteger();
CharsRange elementSegment = findNextValue(newRange(elementStartMark, range.end), readCursor);
Object elementValue = processValue(elementSegment);
array.add(elementValue);
i = readCursor.intValue();
elementStartMark = i + 1;
}
return array;
}
/**
* @param chars
* @return value segment trimmed.
*/
private CharsRange findNextValue(CharsRange chars, AtomicInteger readCursor) {
CharsRange trimChars = chars.trimLeft();
if (trimChars.relativeChar(0) == '{') {
return completeSymbolPair(trimChars, readCursor, "{}");
} else if (trimChars.relativeChar(0) == '[') {
return completeSymbolPair(trimChars, readCursor, "[]");
} else {
int i;
for (i = trimChars.start + 1; i < trimChars.end; i++) {
char ch = json.charAt(i);
if (ch == ',') {
break;
}
}
readCursor.set(i);
return newRange(trimChars.start, i).trim();
}
}
private CharsRange completeSymbolPair(CharsRange trimChars, AtomicInteger readCursor, String symbolPair) {
int leftSymbol = symbolPair.charAt(0);
int rightSymbol = symbolPair.charAt(1);
int symbolsScore = 1;
//nested object
int i;
CharsRange valueSegment = null;
for (i = trimChars.start + 1; i < trimChars.end; i++) {
char ch = json.charAt(i);
if (ch == leftSymbol) {
symbolsScore++;
} else if (ch == rightSymbol) {
symbolsScore--;
}
if (symbolsScore == 0) {
valueSegment = newRange(trimChars.start, i + 1);
break;
}
}
for (; i < trimChars.end; i++) {
char chx = json.charAt(i);
if (chx == ',') {
break;
}
}
readCursor.set(i);
return valueSegment;
}
private Object processValue(CharsRange valueSegment) {
final Object value;
if (valueSegment.relativeChar(0) == '"') {
value = newRange(valueSegment.start + 1, valueSegment.end - 1).toString();
} else if (valueSegment.relativeChar(0) == '{') {
value = processPlainObject(valueSegment);
} else if (valueSegment.relativeChar(0) == '[') {
value = processArray(valueSegment);
} else if (valueSegment.equalsString("true")) {
value = true;
} else if (valueSegment.equalsString("false")) {
value = false;
} else if (valueSegment.equalsString("null")) {
value = null;
} else {
value = Double.parseDouble(valueSegment.toString());
}
return value;
}
static class Property {
final String name;
final Object value;
Property(String name, Object value) {
this.name = name;
this.value = value;
}
static Property of(String name, Object value) {
return new Property(name, value);
}
}
CharsRange newRange(int start, int end) {
return new CharsRange(start, end);
}
class CharsRange {
final int start;
final int end;
CharsRange(int start, int end) {
this.start = start;
this.end = end;
}
CharsRange trimLeft() {
int newStart = -1;
for (int i = start; i < end; i++) {
if (!Character.isWhitespace(json.charAt(i))) {
newStart = i;
break;
}
}
if (newStart == -1) {
throw new IllegalArgumentException("illegal blank string!");
}
return newRange(newStart, end);
}
CharsRange trimRight() {
int newEnd = -1;
for (int i = end - 1; i >= start; i--) {
if (!Character.isWhitespace(json.charAt(i))) {
newEnd = i + 1;
break;
}
}
if (newEnd == -1) {
throw new IllegalArgumentException("illegal blank string!");
}
return newRange(start, newEnd);
}
CharsRange trim() {
return this.trimLeft().trimRight();
}
char relativeChar(int index) {
return json.charAt(start + index);
}
public boolean equalsString(String str) {
return json.regionMatches(true, start, str, 0, str.length());
}
@Override
public String toString() {
return json.subSequence(start, end).toString();
}
}
}
功能测试
junit test
最后当然要做测试,不过我们这个东西是个简单的小东西,暂时不做性能测试,测试一下功能即可。
注意:所有用到的资源都在附件里,下载可直接使用。
package jsonparse;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import io.destinyshine.storks.utils.json.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
/**
* @author liujianyu
* @date 2017/10/17
*/
@Slf4j
public class JsonParserTest {
@Test
public void parseComplexObject() throws IOException, URISyntaxException {
String json = readFile("/json/nested.json");
logger.info("origin json content:{}", json);
JsonParser parser = new JsonParser(json);
Object object = parser.parse();
logger.info("parsed object:{}", object);
}
@Test
public void parseEmptyObject() throws IOException, URISyntaxException {
String json = readFile("/json/empty.json");
logger.info("origin json content:{}", json);
JsonParser parser = new JsonParser(json);
Object object = parser.parse();
logger.info("parsed object:{}", object);
}
@Test
public void parseArray() throws IOException, URISyntaxException {
String json = readFile("/json/array.json");
logger.info("origin json content:{}", json);
JsonParser parser = new JsonParser(json);
Object object = parser.parse();
logger.info("parsed object:{}", object);
}
private String readFile(String resource) throws URISyntaxException, IOException {
return FileUtils.readFileToString(
new File(JsonParserTest.class.getResource(resource).toURI()));
}
}
测试结果
[main] INFO jsonparse.JsonParserTest - parsed array:[{area=12.0, color=green, shape=circle}, {nested={area=12.0, color=green, shape=circle}}]
[main] INFO jsonparse.JsonParserTest - parsed emptyObject:{}
[main] INFO jsonparse.JsonParserTest - parsed complexObject:{parent={address=null, array=[1.0, 3.0, {}], name=jerry, adult=true, age=45.4}, name=tom, adult=false, age=5.0}
结尾
任何功能,简单的实现总是很容易,但是要做到工程级别总是很复杂,一个完整的JSON解析程序会包含更多的特性,比如注解支持、容错性、语法错误提示等。因此我们写这个东西只是自我学习一下,如果真的追求性能和各种特性的支持,还是要用成熟的工具包。
还有,我们的程序没有处理转义字符,不过这个处理倒不是很复杂。
java json parser_自己实现JSON解析器 JsonParser相关推荐
- Java如何解析markdown_使用Java实现的一款Markdown解析器md2x
使用Java实现的一款Markdown解析器md2x 前段时间在写自己的博客程序的时候,在前台使用了marked.js来解析自己的markdown文章,然后发现在进入文章页面的时候总会闪烁一下(前台解 ...
- 用c#写一个json的万能解析器
CommonJsonModel .cs /// <summary>/// 万能JSON解析器/// </summary>public class CommonJsonModel ...
- java sax_Java教程分享详细了解Javasax解析器
Java教程分享详细了解Javasax解析器,SAX(针对XML的简单API)是基于事件为XML文档的解析器.不像DOM解析器,SAX解析器创建没有解析树.SAX是一个流接口用于XML的,这意味着使用 ...
- Ajax和JSON-学习笔记04【JSON_解析器】
Java后端 学习路线 笔记汇总表[黑马程序员] Ajax和JSON-学习笔记01[原生JS方式实现Ajax] Ajax和JSON-学习笔记02[JQuery方式实现Ajax] Ajax和JSON-学 ...
- java xml开源操作类,xml解析和操作的开源工具项目涵盖java c++ php 等语言
XML解析器-Xerces XML操作库-dom4j XML文档解析器-Digester J2ME-的XML-解析器-kXML XML解析类库-MXP1 XML解析器-LibX ...
- 在线JSONPath解析器
类似于XPath在xml文档中的定位,JsonPath表达式通常是用来路径检索或设置Json的.JsonPath是一种简单的方法来提取给定JSON文档的部分内容. JsonPath有许多编程语言,如J ...
- Spring5利用视图解析器生成Excel文件
介绍 请求处理方法执行完成后,最终返回一个ModelAndView对象.对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 M ...
- rest-framework 解析器
一 解析器的作用: 根据请求头 content-type 选择对应的解析器对请求体内容进行处理. 有application/json,x-www-form-urlencoded,form-data等格 ...
- rest_framework之解析器详解 05
解析器就是服务端写api,对于前端用户发来的数据进行解析.解析完之后拿到自己能用数据. 本质就是对请求体中的数据进行解析. django的解析器 post请求过来之后,django 的request. ...
最新文章
- linux内核rcu锁实例,Linux Rcu到底有没有锁?
- 1034 Head of a Gang(图的DFS解法) 擦边大法好
- 那些没说出口的研发之痛,做与不做微服务的几大理由
- php fsockopen socket,PHP利用socket(fsockopen)的简单示例
- linux下编译的PHP添加soap扩展
- 算法(algorithm)、模型(model)与框架(framework)
- Nginx+Tomcat代理环境下JS无法完全加载问题
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_23-页面预览-页面预览开发
- matlab 复杂曲面,基于MATLAB生成的SCR文件实现AutoCAD中复杂曲线曲面的绘制.pdf
- swift - enumerated()
- 荐书:「时差党」——出国留学不是一件容易的事
- UA MATH563 概率论的数学基础 中心极限定理19 概率测度的全变差收敛 Skorohod定理
- 使用较广泛的安全测试工具有哪些?
- Cz工具集使用介绍 - 规范Git提交说明
- PHP一行代码实现项目绑定域名从而保护源代码
- 【Python】实现模拟股票交易的虚拟盘小工具(巴菲特模拟器)
- 北方民族大学计算机网络实验,2017年北方民族大学计算机应用技术843计算机网络考研强化模拟题...
- 烛照幽荧是鸿蒙时期,上古圣兽:太阳烛照与太阴幽荧
- 视频化表达“吞噬”互联网,文本信息逐渐被替代?
- ActiveSync Startup Server
热门文章
- Telnet 1521端口连接失败问题,经过四天的努力终于解决!
- 软件工程(四)——结构化设计之总体设计
- zynq+linux+ramdisk can调试
- 赛灵思运行linux,玩转赛灵思Zedboard开发板(6):如何在Zedboard上运行linux下的应用程序?...
- 北京大学2017年数学分析考研试题
- 软件测试面试题:对杯子进行测试用例设计?
- golang力扣leetcode 1823.找出游戏的获胜者
- 一加7t人脸识别_一加7T新机设计图发布 这款手机的外观设计如何
- JavaScript增加和删除DOM节点
- C++代码静态分析与优化(7)_vera++