自己实现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相关推荐

  1. Java如何解析markdown_使用Java实现的一款Markdown解析器md2x

    使用Java实现的一款Markdown解析器md2x 前段时间在写自己的博客程序的时候,在前台使用了marked.js来解析自己的markdown文章,然后发现在进入文章页面的时候总会闪烁一下(前台解 ...

  2. 用c#写一个json的万能解析器

    CommonJsonModel .cs /// <summary>/// 万能JSON解析器/// </summary>public class CommonJsonModel ...

  3. java sax_Java教程分享详细了解Javasax解析器

    Java教程分享详细了解Javasax解析器,SAX(针对XML的简单API)是基于事件为XML文档的解析器.不像DOM解析器,SAX解析器创建没有解析树.SAX是一个流接口用于XML的,这意味着使用 ...

  4. Ajax和JSON-学习笔记04【JSON_解析器】

    Java后端 学习路线 笔记汇总表[黑马程序员] Ajax和JSON-学习笔记01[原生JS方式实现Ajax] Ajax和JSON-学习笔记02[JQuery方式实现Ajax] Ajax和JSON-学 ...

  5. java xml开源操作类,xml解析和操作的开源工具项目涵盖java c++ php 等语言

    XML解析器-Xerces    XML操作库-dom4j    XML文档解析器-Digester    J2ME-的XML-解析器-kXML XML解析类库-MXP1    XML解析器-LibX ...

  6. 在线JSONPath解析器

    类似于XPath在xml文档中的定位,JsonPath表达式通常是用来路径检索或设置Json的.JsonPath是一种简单的方法来提取给定JSON文档的部分内容. JsonPath有许多编程语言,如J ...

  7. Spring5利用视图解析器生成Excel文件

    介绍 请求处理方法执行完成后,最终返回一个ModelAndView对象.对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 M ...

  8. rest-framework 解析器

    一 解析器的作用: 根据请求头 content-type 选择对应的解析器对请求体内容进行处理. 有application/json,x-www-form-urlencoded,form-data等格 ...

  9. rest_framework之解析器详解 05

    解析器就是服务端写api,对于前端用户发来的数据进行解析.解析完之后拿到自己能用数据. 本质就是对请求体中的数据进行解析. django的解析器 post请求过来之后,django 的request. ...

最新文章

  1. linux内核rcu锁实例,Linux Rcu到底有没有锁?
  2. 1034 Head of a Gang(图的DFS解法) 擦边大法好
  3. 那些没说出口的研发之痛,做与不做微服务的几大理由
  4. php fsockopen socket,PHP利用socket(fsockopen)的简单示例
  5. linux下编译的PHP添加soap扩展
  6. 算法(algorithm)、模型(model)与框架(framework)
  7. Nginx+Tomcat代理环境下JS无法完全加载问题
  8. 阶段5 3.微服务项目【学成在线】_day04 页面静态化_23-页面预览-页面预览开发
  9. matlab 复杂曲面,基于MATLAB生成的SCR文件实现AutoCAD中复杂曲线曲面的绘制.pdf
  10. swift - enumerated()
  11. 荐书:「时差党」——出国留学不是一件容易的事
  12. UA MATH563 概率论的数学基础 中心极限定理19 概率测度的全变差收敛 Skorohod定理
  13. 使用较广泛的安全测试工具有哪些?
  14. Cz工具集使用介绍 - 规范Git提交说明
  15. PHP一行代码实现项目绑定域名从而保护源代码
  16. 【Python】实现模拟股票交易的虚拟盘小工具(巴菲特模拟器)
  17. 北方民族大学计算机网络实验,2017年北方民族大学计算机应用技术843计算机网络考研强化模拟题...
  18. 烛照幽荧是鸿蒙时期,上古圣兽:太阳烛照与太阴幽荧
  19. 视频化表达“吞噬”互联网,文本信息逐渐被替代?
  20. ActiveSync Startup Server

热门文章

  1. Telnet 1521端口连接失败问题,经过四天的努力终于解决!
  2. 软件工程(四)——结构化设计之总体设计
  3. zynq+linux+ramdisk can调试
  4. 赛灵思运行linux,玩转赛灵思Zedboard开发板(6):如何在Zedboard上运行linux下的应用程序?...
  5. 北京大学2017年数学分析考研试题
  6. 软件测试面试题:对杯子进行测试用例设计?
  7. golang力扣leetcode 1823.找出游戏的获胜者
  8. 一加7t人脸识别_一加7T新机设计图发布 这款手机的外观设计如何
  9. JavaScript增加和删除DOM节点
  10. C++代码静态分析与优化(7)_vera++