自己實(shí)現(xiàn)json parser,只有一個(gè)類(lèi)费就,不依賴(lài)任何第三方工具。
背景
為什么要實(shí)現(xiàn)json解析器呢川队?在我實(shí)現(xiàn)一個(gè)rpc框架的過(guò)程中力细,注冊(cè)中心部分使用consul,而consul的api是通過(guò)restful http api來(lái)提供的固额,數(shù)據(jù)交互格式為json眠蚂,此時(shí)就需要用到j(luò)son解析工具。
讓我們回顧一下java界較為通用的json處理庫(kù)斗躏,常用的json處理庫(kù)有jackson逝慧,gson,fastjson啄糙,其他還有許多json工具包笛臣,不過(guò)都不流行或已退出歷史舞臺(tái)。
java ee也有一個(gè)相關(guān)的jsr隧饼,jsr 353 json processing api沈堡,定義了通用的json 處理 api,一個(gè)參考實(shí)現(xiàn)是oracle 的glassfish jsonp燕雁。
jackson是最完善的json處理工具诞丽,實(shí)現(xiàn)的功能最多,并且支持jaxb注解拐格,而且也有支持適配 jsr353 json processing api的模塊僧免。。也是我個(gè)人最喜歡的json處理工具包捏浊。其實(shí)fastjson的很多實(shí)現(xiàn)的常量定義都能看到j(luò)axkson的影子懂衩。
本來(lái)決定使用jackson的,一般來(lái)講現(xiàn)在spring是企業(yè)應(yīng)用的標(biāo)配,而spring mvc應(yīng)用通常都會(huì)依賴(lài)jackson的包勃痴。因此依賴(lài)一下jackson的包也可以接受谒所。但后來(lái)又考慮了幾次,覺(jué)得依賴(lài)第三方的包畢竟不美沛申,額外帶來(lái)依賴(lài)總是會(huì)增加復(fù)雜性劣领,對(duì)于基礎(chǔ)組件,除了日志門(mén)面框架這樣與實(shí)現(xiàn)無(wú)關(guān)的包铁材,還是盡量少依賴(lài)其他庫(kù)為妙尖淘。
因此決定自己寫(xiě)一個(gè)json解析工具,考慮了一下覺(jué)得也不是十分復(fù)雜著觉,用遞歸的方式解析json串即可村生。具體實(shí)現(xiàn)思路接下來(lái)分析。
實(shí)現(xiàn)思路
json的結(jié)構(gòu)分析
json的結(jié)構(gòu)包含幾種元素:
- object(name value pair object)饼丘,此處指狹義的對(duì)象趁桃,名值對(duì)形式。廣義上任何元素都是對(duì)象肄鸽。
- array卫病,由[]符號(hào)包裹,元素用英文逗號(hào)分隔典徘。
- 字面類(lèi)型蟀苛,字面類(lèi)型最為簡(jiǎn)單,不能再嵌套
- number
- boolean逮诲,true or false
- null
下面放幾張json.org的圖帜平,以直觀的形式展示json格式。
object 內(nèi)部的value和array內(nèi)部的元素都可以是任意組成類(lèi)型梅鹦,可以存在任意層次的嵌套裆甩。因此用遞歸方式解析比較簡(jiǎn)單。
實(shí)現(xiàn)思路
a) 基本概念
- <span style="color:red">trim</span> 帘瞭,
trim
是把一個(gè)字符序列的頭尾的不可見(jiàn)字符去掉淑掌,由于json允許在元素之間存在任意個(gè)tab、換行蝶念、空格抛腕。因此可能有許多地方需要用到trim
。
b) processObjcet: object解析
- 以"
{
"開(kāi)始媒殉,正確找到對(duì)應(yīng)的結(jié)束的"}
"担敌,由于花括號(hào)可能存在多重嵌套,找到正確的結(jié)束符號(hào)是有技巧的廷蓉。記下整個(gè){}區(qū)塊的位置全封。 - 脫去頭尾的
{}
马昙,中間的部分是properties列表,以name:value,...
的形式存儲(chǔ)刹悴。解析properties列表行楞。 - 標(biāo)記
nameStartMark
,初始為0土匀,遇到冒號(hào)":
"子房,從nameStartMark
到冒號(hào)前都為nameToken
(需要trim),從冒號(hào)后開(kāi)始尋找nextValue
就轧。同樣需要注意(1.)提及的花括號(hào)和中括號(hào)匹配证杭,遇到逗號(hào)或結(jié)束表示value區(qū)塊結(jié)束。(由findNextValue函數(shù)完成) - 移動(dòng)游標(biāo)到找到的value區(qū)塊后妒御,并更新
nameStartMark
標(biāo)記解愤。 - 循環(huán)執(zhí)行(3.)和(4.),直到不再有冒號(hào)乎莉。
- <span style="color:red">注意:</span>找到的value區(qū)塊移交給另一個(gè)函數(shù)
processValue
處理送讲,此處存在遞歸。
c) processArray: array解析
與object類(lèi)似惋啃,但是比object簡(jiǎn)單
- 以"
[
"開(kāi)始李茫,正確找到對(duì)應(yīng)的結(jié)束的"]
",由于方括號(hào)可能存在多重嵌套肥橙,找到正確的結(jié)束符號(hào)是有技巧的。記下整個(gè)[]
區(qū)塊的位置秸侣。 - 脫去頭尾的
[]
存筏,中間的部分是elements列表,以element1,element2...
的形式存儲(chǔ)味榛。 - 直接循環(huán)執(zhí)行findNextValue即可椭坚,直到結(jié)束
- <span style="color:red">注意:</span>同上,找到的value區(qū)塊移交給另一個(gè)函數(shù)
processValue
處理搏色,此處存在遞歸善茎。
d) processValue: value解析
此處是一個(gè)遞歸操作,value本身可能是一個(gè)字面量频轿,或者是object垂涯,或者是array。
- 如果value區(qū)塊以"
{
"開(kāi)頭航邢,則是object耕赘,移交給processObjcet
做object解析,遞歸操作膳殷。 - 如果value區(qū)塊以"
[
"開(kāi)頭操骡,則是array,移交給processArray
做array解析,遞歸操作册招。 - 字面量岔激,如string,boolean是掰,number虑鼎,null直接解析。string可能有轉(zhuǎn)義字符冀惭,這個(gè)目前沒(méi)有考慮處理震叙。
e) completeSymbolPair: 尋找匹配的{}
和[]
由于{}
和[]
都可能存在多重嵌套,因此需要正確的找到一個(gè)開(kāi)始的花括號(hào)對(duì)應(yīng)的結(jié)束符號(hào)散休,方括號(hào)同理媒楼。
這個(gè)可以用這個(gè)原理:符號(hào)一定是成對(duì)出現(xiàn)的。
步驟如下:
- 對(duì)于已知的第一個(gè)
左符號(hào)
戚丸,定義symbolsScore=1
划址,index=1
, - 遍歷后續(xù)的字符限府,遇到
左符號(hào)
則symbolsScore++
夺颤,遇到右符號(hào)
則symbolsScore--
。 - 直到symbolsScore==0胁勺,則找到正確的結(jié)束符世澜。
- 左邊
開(kāi)始符號(hào)
到右邊結(jié)束符號(hào)
之間的內(nèi)容就是需要的內(nèi)容。
代碼實(shí)現(xiàn)
方法原型
public class JsonParser {
private final String json;
/**
* 入口方法
* @return 解析完成的對(duì)象
*/
public Object parse() {
CharsRange trimmedJson = newRange(0, json.length()).trim();
return processValue(trimmedJson);
}
private Object processPlainObject(CharsRange range) {}
private List<Property> 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;}
}
具體代碼
此處當(dāng)然要放具體的代碼署穗,只有一個(gè)類(lèi)寥裂。
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 解析完成的對(duì)象
*/
public Object parse() {
CharsRange trimmedJson = newRange(0, json.length()).trim();
return processValue(trimmedJson);
}
private Object processPlainObject(CharsRange range) {
List<Property> properties = processProperties(newRange(range.start + 1, range.end - 1));
Map<String, Object> object = new HashMap<>();
properties.forEach(prop -> object.put(prop.name, prop.value));
return object;
}
private List<Property> processProperties(CharsRange range) {
List<Property> 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<Object> 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();
}
}
}
功能測(cè)試
junit test
最后當(dāng)然要做測(cè)試,不過(guò)我們這個(gè)東西是個(gè)簡(jiǎn)單的小東西案疲,暫時(shí)不做性能測(cè)試封恰,測(cè)試一下功能即可。
注意:所有用到的資源都在附件里褐啡,下載可直接使用诺舔。
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()));
}
}
測(cè)試結(jié)果
[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}
結(jié)尾
任何功能,簡(jiǎn)單的實(shí)現(xiàn)總是很容易备畦,但是要做到工程級(jí)別總是很復(fù)雜低飒,一個(gè)完整的JSON解析程序會(huì)包含更多的特性,比如注解支持萍恕、容錯(cuò)性逸嘀、語(yǔ)法錯(cuò)誤提示等。因此我們寫(xiě)這個(gè)東西只是自我學(xué)習(xí)一下允粤,如果真的追求性能和各種特性的支持崭倘,還是要用成熟的工具包翼岁。
還有,我們的程序沒(méi)有處理轉(zhuǎn)義字符司光,不過(guò)這個(gè)處理倒不是很復(fù)雜琅坡。