持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
一个JSON解析器实际上就是一个方法,它的输入是一个表示JSON的字符串,输出是结构化的对应到语言本身的数据结构。
一般来说,解析过程包括词法分析和语法分析两个阶段。词法分析阶段的目标是按照构词规则将 JSON字符串解析成 Token 流,比如有如下的 JSON 字符串:
{
"key":"value",
...
}
复制代码
经过词法分析后,得到一组 Token,如下:
词法分析解析出 Token 序列后,接下来要进行语法分析。语法分析的目的是根据 JSON 文法检查上面Token 序列所构成的 JSON 结构是否合法。比如 JSON 文法要求非空 JSON 对象以键值对的形式出现,形如 object = {string : value}。如果传入了一个格式错误的字符串,比如:
{
"key","value"
}
复制代码
那么在语法分析阶段,语法分析器分析完 Token name后,认为它是一个符合规则的 Token,并且认为它是一个键。接下来,语法分析器读取下一个 Token,期望这个 Token 是:
。但当它读取了这个Token,发现这个 Token 是,
,并非其期望的:
,于是文法分析器就会报错误。
1 JSON解析分析小结
- 通过词法分析是将字符串解析成一组 Token 序列
- 然后通过语法分析检查输入的 Token 序列所构成的 JSON 格式是否合法
2 词法分析
按照“构词规则”将 JSON 字符串解析成 Token 流。请注意双引号引起来的词–构词规则,所谓构词规则是指词法分析模块在将字符串解析成 Token 时所参考的规则。在 JSON 中,构词规则对应于几种数据类型,当词法解析器读入某个词,且这个词类型符合 JSON 所规定的数据类型时,词法分析器认为这个词符合构词规则,就会生成相应的 Token。这里我们可以参考www.json.org/对 JSON 的定义,罗列一下 JSON 所规定的数据类型:
BEGIN_OBJECT | { |
---|---|
END_OBJECT | } |
BEGIN_ARRAY | [ |
END_ARRAY | ] |
NULL | null |
NUMBER | 数字 |
STRING | 字符串 |
BOOLEAN | true/false |
SEP_COLON | : |
SEP_COMMA | , |
END_DOCUMENT | 文档结束 |
当词法分析器读取的词是上面类型中的一种时,即可将其解析成一个 Token。我们可以定义一个枚举类来表示上面的数据类型,如下:
public enum TokenType {
BEGIN_OBJECT(1),
END_OBJECT(2),
BEGIN_ARRAY(4),
END_ARRAY(8),
NULL(16),
NUMBER(32),
STRING(64),
BOOLEAN(128),
SEP_COLON(256),
SEP_COMMA(512),
END_DOCUMENT(1024);
private int code;
TokenType(int code) {
this.code = code;
}
public int getTokenCode() {
return code;
}
}
复制代码
在解析过程中,仅有 TokenType 类型还不行。我们除了要将某个词的类型保存起来,还需要保存这个词的字面量。所以,所以这里还需要定义一个 Token 类。用于封装词类型和字面量,如下:
public class Token {
private TokenType tokenType;
private String value;
public Token(TokenType tokenType, String value) {
this.tokenType = tokenType;
this.value = value;
}
public TokenType getTokenType() {
return tokenType;
}
public void setTokenType(TokenType tokenType) {
this.tokenType = tokenType;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "Token{" +
"tokenType=" + tokenType +
", value='" + value + ''' +
'}';
}
}
复制代码
定义好了 Token 类,接下来再来定义一个读取字符串的类:
public class CharReader {
private static final int BUFFER_SIZE = 1024;
private final Reader reader;
private final char[] buffer;
private int pos;
private int size;
public CharReader(Reader reader) {
this.reader = reader;
buffer = new char[BUFFER_SIZE];
}
/**
* 返回pos下标处的字符
*
* @return 字符
*/
public char peek() {
if (pos - 1 >= size) {
//越界 返回-1
return (char) -1;
}
//用Math.max()确保索引不会为负值
return buffer[Math.max(0, pos - 1)];
}
/**
* 返回pos下标处的字符 并将pos+1
*
* @return 字符
*/
public char next() {
if (!hasMore()) {
return (char) -1;
}
return buffer[pos++];
}
public void back() {
pos = Math.max(0, --pos);
}
private boolean hasMore() {
if (pos < size) {
return true;
}
fillBuffer();
return pos < size;
}
private void fillBuffer() {
try {
int n = reader.read(buffer);
if (n == -1) {
return;
}
pos = 0;
size = n;
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
有了 TokenType、Token 和 CharReader 这三个辅助类,接下来我们就可以实现词法解析器了:
public class Tokenizer {
private CharReader charReader;
private TokenList tokens;
public TokenList tokenize(CharReader charReader) throws IOException {
this.charReader = charReader;
tokens = new TokenList();
tokenize();
return tokens;
}
private void tokenize() throws IOException {
// 使用do-while处理空文件
Token token;
do {
token = start();
tokens.add(token);
} while (token.getTokenType() != TokenType.END_DOCUMENT);
}
private Token start() throws IOException {
char ch;
for (; ; ) {
if (!charReader.hasMore()) {
return new Token(TokenType.END_DOCUMENT, null);
}
ch = charReader.next();
if (!isWhiteSpace(ch)) {
break;
}
}
/**
* n ➔ null
* t ➔ true
* f ➔ false
* " ➔ string
* 0-9/- ➔ number
* [ ➔ array
* { ➔ object
*/
switch (ch) {
case '{':
return new Token(TokenType.BEGIN_OBJECT, String.valueOf(ch));
case '}':
return new Token(TokenType.END_OBJECT, String.valueOf(ch));
case '[':
return new Token(TokenType.BEGIN_ARRAY, String.valueOf(ch));
case ']':
return new Token(TokenType.END_ARRAY, String.valueOf(ch));
case ',':
return new Token(TokenType.SEP_COMMA, String.valueOf(ch));
case ':':
return new Token(TokenType.SEP_COLON, String.valueOf(ch));
case 'n':
return readNull();
case 't':
case 'f':
return readBoolean();
case '"':
return readString();
case '-':
return readNumber();
}
if (isDigit(ch)) {
return readNumber();
}
throw new JsonParseException("Illegal character");
}
private boolean isWhiteSpace(char ch) {
return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
}
private Token readString() throws IOException {
StringBuilder sb = new StringBuilder();
for (; ; ) {
char ch = charReader.next();
if (ch == '\') {
if (!isEscape()) {
throw new JsonParseException("Invalid escape character");
}
sb.append('\');
ch = charReader.peek();
sb.append(ch);
if (ch == 'u') {
for (int i = 0; i < 4; i++) {
ch = charReader.next();
if (isHex(ch)) {
sb.append(ch);
} else {
throw new JsonParseException("Invalid character");
}
}
}
} else if (ch == '"') {
return new Token(TokenType.STRING, sb.toString());
} else if (ch == '\r' || ch == '\n') {
throw new JsonParseException("Invalid character");
} else {
sb.append(ch);
}
}
}
private boolean isEscape() throws IOException {
char ch = charReader.next();
return (ch == '"' || ch == '\' || ch == 'u' || ch == 'r'
|| ch == 'n' || ch == 'b' || ch == 't' || ch == 'f');
}
private boolean isHex(char ch) {
return ((ch >= '0' && ch <= '9') || ('a' <= ch && ch <= 'f')
|| ('A' <= ch && ch <= 'F'));
}
private Token readNumber() throws IOException {
char ch = charReader.peek();
StringBuilder sb = new StringBuilder();
if (ch == '-') { // 处理负数
sb.append(ch);
ch = charReader.next();
if (ch == '0') { // 处理 -0.xxxx
sb.append(ch);
sb.append(readFracAndExp());
} else if (isDigitOne2Nine(ch)) {
do {
sb.append(ch);
ch = charReader.next();
} while (isDigit(ch));
if (ch != (char) -1) {
charReader.back();
sb.append(readFracAndExp());
}
} else {
throw new JsonParseException("Invalid minus number");
}
} else if (ch == '0') { // 处理小数
sb.append(ch);
sb.append(readFracAndExp());
} else {
do {
sb.append(ch);
ch = charReader.next();
} while (isDigit(ch));
if (ch != (char) -1) {
charReader.back();
sb.append(readFracAndExp());
}
}
return new Token(TokenType.NUMBER, sb.toString());
}
private boolean isExp(char ch) throws IOException {
return ch == 'e' || ch == 'E';
}
private boolean isDigit(char ch) {
return ch >= '0' && ch <= '9';
}
private boolean isDigitOne2Nine(char ch) {
return ch >= '0' && ch <= '9';
}
private String readFracAndExp() throws IOException {
StringBuilder sb = new StringBuilder();
char ch = charReader.next();
if (ch == '.') {
sb.append(ch);
ch = charReader.next();
if (!isDigit(ch)) {
throw new JsonParseException("Invalid frac");
}
do {
sb.append(ch);
ch = charReader.next();
} while (isDigit(ch));
if (isExp(ch)) { // 处理科学计数法
sb.append(ch);
sb.append(readExp());
} else {
if (ch != (char) -1) {
charReader.back();
}
}
} else if (isExp(ch)) {
sb.append(ch);
sb.append(readExp());
} else {
charReader.back();
}
return sb.toString();
}
private String readExp() throws IOException {
StringBuilder sb = new StringBuilder();
char ch = charReader.next();
if (ch == '+' || ch == '-') {
sb.append(ch);
ch = charReader.next();
if (isDigit(ch)) {
do {
sb.append(ch);
ch = charReader.next();
} while (isDigit(ch));
if (ch != (char) -1) { // 读取结束,不用回退
charReader.back();
}
} else {
throw new JsonParseException("e or E");
}
} else {
throw new JsonParseException("e or E");
}
return sb.toString();
}
private Token readBoolean() throws IOException {
if (charReader.peek() == 't') {
if (!(charReader.next() == 'r' && charReader.next() == 'u' && charReader.next() == 'e')) {
throw new JsonParseException("Invalid json string");
}
return new Token(TokenType.BOOLEAN, "true");
} else {
if (!(charReader.next() == 'a' && charReader.next() == 'l'
&& charReader.next() == 's' && charReader.next() == 'e')) {
throw new JsonParseException("Invalid json string");
}
return new Token(TokenType.BOOLEAN, "false");
}
}
private Token readNull() throws IOException {
if (!(charReader.next() == 'u' && charReader.next() == 'l' && charReader.next() == 'l')) {
throw new JsonParseException("Invalid json string");
}
return new Token(TokenType.NULL, "null");
}
}
复制代码
核心方法 start,这个方法代码量不多,并不复杂。其通过一个死循环不停的读取字符,然后再根据字符的类型,执行不同的解析逻辑。上面说过,JSON 的解析过程比较简单。原因在于,在解析时,只需通过每个词第一个字符即可判断出这个词的 Token Type。比如:
- 第一个字符是{、}、[、]、,、:,直接封装成相应的 Token 返回即可
- 第一个字符是n,期望这个词是null,Token 类型是NULL
- 第一个字符是t或f,期望这个词是true或者false,Token 类型是 BOOLEAN
- 第一个字符是”,期望这个词是字符串,Token 类型为String
- 第一个字符是0~9或-,期望这个词是数字,类型为NUMBER
正如上面所说,词法分析器只需要根据每个词的第一个字符,即可知道接下来它所期望读取的到的内容是什么样的。如果满足期望了,则返回 Token,否则返回错误。下面就来看看词法解析器在碰到第一个字符是n和”时的处理过程。先看碰到字符n的处理过程:
private Token readNull() throws IOException {
if (!(charReader.next() == 'u' && charReader.next() == 'l' && charReader.next() == 'l')) {
throw new JsonParseException("Invalid json string");
}
return new Token(TokenType.NULL, "null");
}
复制代码
上面的代码很简单,词法分析器在读取字符n后,期望后面的三个字符分别是u,l,l,与 n 组成词 null。如果满足期望,则返回类型为 NULL 的 Token,否则报异常。
接下来看看 string 类型的数据处理过程:
private Token readString() throws IOException {
StringBuilder sb = new StringBuilder();
for (; ; ) {
char ch = charReader.next();
if (ch == '\') {
if (!isEscape()) {
throw new JsonParseException("Invalid escape character");
}
sb.append('\');
ch = charReader.peek();
sb.append(ch);
if (ch == 'u') {
for (int i = 0; i < 4; i++) {
ch = charReader.next();
if (isHex(ch)) {
sb.append(ch);
} else {
throw new JsonParseException("Invalid character");
}
}
}
} else if (ch == '"') {
return new Token(TokenType.STRING, sb.toString());
} else if (ch == '\r' || ch == '\n') {
throw new JsonParseException("Invalid character");
} else {
sb.append(ch);
}
}
}
private boolean isEscape() throws IOException {
char ch = charReader.next();
return (ch == '"' || ch == '\' || ch == 'u' || ch == 'r'
|| ch == 'n' || ch == 'b' || ch == 't' || ch == 'f');
}
private boolean isHex(char ch) {
return ((ch >= '0' && ch <= '9') || ('a' <= ch && ch <= 'f')
|| ('A' <= ch && ch <= 'F'));
}
复制代码
String 类型的数据解析起来要稍微复杂一些,主要是需要处理一些特殊类型的字符。JSON 所允许的特殊类型的字符如下:
"
\
\b
\f
\n
\r
\t
\u four-hex-digits
/
复制代码
最后一种特殊字符/代码中未做处理,其他字符均做了判断,判断逻辑在 isEscape 方法中。在传入JSON 字符串中,仅允许字符串包含上面所列的转义字符。如果乱传转义字符,解析时会报错。对于STRING 类型的词,解析过程始于字符"
,也终于"
。所以在解析的过程中,当再次遇到字符"
, readString 方法会认为本次的字符串解析过程结束,并返回相应类型的 Token。
3 语法分析
当词法分析结束后,且分析过程中没有抛出错误,那么接下来就可以进行语法分析了。语法分析过程以词法分析阶段解析出的 Token 序列作为输入,输出 JSON Object 或 JSON Array。
当词法分析结束后,且分析过程中没有抛出错误,那么接下来就可以进行语法分析了。语法分析过程以词法分析阶段解析出的 Token 序列作为输入,输出 JSON Object 或 JSON Array。语法分析器的实现的文法如下:
object = {}
| { members }
members = pair
| pair , members
pair = string : value
array = []
| [ elements ]
elements = value
| value , elements
value = string
| number
| object
| array
| true
| false
| null
string = ""
| " chars "
chars = char
| char chars
char = any-Unicode-character-except-"-or--or- control-character
| "
| \
| /
| \b
| \f
| \n
| \r
| \t
| \u four-hex-digits
number = int
| int frac
| int exp
| int frac exp
int = digit
| digit1-9 digits
| - digit
| - digit1-9 digits
frac = . digits
exp = e digits
digits = digit
| digit digits
e = e
| e+
| e-
| E
| E+
| E-
复制代码
语法分析器的实现需要借助两个辅助类,也就是语法分析器的输出类,分别是 JsonObject 和JsonArray。
代码如下:
public class JsonObject {
private Map<String, Object> map = new HashMap<>();
public void put(String key, Object value) {
map.put(key, value);
}
public Object get(String key) {
return map.get(key);
}
public List<Map.Entry<String, Object>> getAllKeyValue() {
return new ArrayList<>(map.entrySet());
}
public JsonObject getJsonObject(String key) {
if (!map.containsKey(key)) {
throw new IllegalArgumentException("Invalid key");
}
Object obj = map.get(key);
if (!(obj instanceof JsonObject)) {
throw new JsonTypeException("Type of value is not JsonObject");
}
return (JsonObject) obj;
}
public JsonArray getJsonArray(String key) {
if (!map.containsKey(key)) {
throw new JsonTypeException("Invalid key");
}
Object obj = map.get(key);
if (!(obj instanceof JsonArray)) {
throw new JsonTypeException("Type of value is not JsonObject");
}
return (JsonArray) obj;
}
@Override
public String toString() {
return JsonUtils.beautify(this);
}
}
复制代码
public class JsonArray {
private List list = new ArrayList();
public void add(Object obj) {
list.add(obj);
}
public Object get(int index) {
return list.get(index);
}
public int size() {
return list.size();
}
public JsonObject getJsonObject(int index) {
Object obj = list.get(index);
if (!(obj instanceof JsonObject)) {
throw new JsonTypeException("Type of value is not JsonObject");
}
return (JsonObject) obj;
}
public JsonArray getJsonArray(int index) {
Object obj = list.get(index);
if (!(obj instanceof JsonArray)) {
throw new JsonTypeException("Type of value is not JsonArray");
}
return (JsonArray) obj;
}
@Override
public String toString() {
return JsonUtils.beautify(this);
}
public Iterator iterator() {
return list.iterator();
}
}
复制代码
语法解析器的核心逻辑封装在了 parseJsonObject 和 parseJsonArray 两个方法中:
public class Parser {
private static final int BEGIN_OBJECT_TOKEN = 1;
private static final int END_OBJECT_TOKEN = 2;
private static final int BEGIN_ARRAY_TOKEN = 4;
private static final int END_ARRAY_TOKEN = 8;
private static final int NULL_TOKEN = 16;
private static final int NUMBER_TOKEN = 32;
private static final int STRING_TOKEN = 64;
private static final int BOOLEAN_TOKEN = 128;
private static final int SEP_COLON_TOKEN = 256;
private static final int SEP_COMMA_TOKEN = 512;
private TokenList tokens;
public Object parse(TokenList tokens) {
this.tokens = tokens;
return parse();
}
private Object parse() {
Token token = tokens.next();
if (token == null) {
return new JsonObject();
} else if (token.getTokenType() == TokenType.BEGIN_OBJECT) {
return parseJsonObject();
} else if (token.getTokenType() == TokenType.BEGIN_ARRAY) {
return parseJsonArray();
} else {
throw new JsonParseException("Parse error, invalid Token.");
}
}
private JsonObject parseJsonObject() {
JsonObject jsonObject = new JsonObject();
int expectToken = STRING_TOKEN | END_OBJECT_TOKEN;
String key = null;
Object value = null;
while (tokens.hasMore()) {
Token token = tokens.next();
TokenType tokenType = token.getTokenType();
String tokenValue = token.getValue();
switch (tokenType) {
case BEGIN_OBJECT:
checkExpectToken(tokenType, expectToken);
jsonObject.put(key, parseJsonObject()); // 递归解析 json object
expectToken = SEP_COMMA_TOKEN | END_OBJECT_TOKEN;
break;
case END_OBJECT:
checkExpectToken(tokenType, expectToken);
return jsonObject;
case BEGIN_ARRAY: // 解析 json array
checkExpectToken(tokenType, expectToken);
jsonObject.put(key, parseJsonArray());
expectToken = SEP_COMMA_TOKEN | END_OBJECT_TOKEN;
break;
case NULL:
checkExpectToken(tokenType, expectToken);
jsonObject.put(key, null);
expectToken = SEP_COMMA_TOKEN | END_OBJECT_TOKEN;
break;
case NUMBER:
checkExpectToken(tokenType, expectToken);
if (tokenValue.contains(".") || tokenValue.contains("e") || tokenValue.contains("E")) {
jsonObject.put(key, Double.valueOf(tokenValue));
} else {
Long num = Long.valueOf(tokenValue);
if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE) {
jsonObject.put(key, num);
} else {
jsonObject.put(key, num.intValue());
}
}
expectToken = SEP_COMMA_TOKEN | END_OBJECT_TOKEN;
break;
case BOOLEAN:
checkExpectToken(tokenType, expectToken);
jsonObject.put(key, Boolean.valueOf(token.getValue()));
expectToken = SEP_COMMA_TOKEN | END_OBJECT_TOKEN;
break;
case STRING:
checkExpectToken(tokenType, expectToken);
Token preToken = tokens.peekPrevious();
/*
* 在 JSON 中,字符串既可以作为键,也可作为值。
* 作为键时,只期待下一个 Token 类型为 SEP_COLON。
* 作为值时,期待下一个 Token 类型为 SEP_COMMA 或 END_OBJECT
*/
if (preToken.getTokenType() == TokenType.SEP_COLON) {
value = token.getValue();
jsonObject.put(key, value);
expectToken = SEP_COMMA_TOKEN | END_OBJECT_TOKEN;
} else {
key = token.getValue();
expectToken = SEP_COLON_TOKEN;
}
break;
case SEP_COLON:
checkExpectToken(tokenType, expectToken);
expectToken = NULL_TOKEN | NUMBER_TOKEN | BOOLEAN_TOKEN | STRING_TOKEN
| BEGIN_OBJECT_TOKEN | BEGIN_ARRAY_TOKEN;
break;
case SEP_COMMA:
checkExpectToken(tokenType, expectToken);
expectToken = STRING_TOKEN;
break;
case END_DOCUMENT:
checkExpectToken(tokenType, expectToken);
return jsonObject;
default:
throw new JsonParseException("Unexpected Token.");
}
}
throw new JsonParseException("Parse error, invalid Token.");
}
private JsonArray parseJsonArray() {
int expectToken = BEGIN_ARRAY_TOKEN | END_ARRAY_TOKEN | BEGIN_OBJECT_TOKEN | NULL_TOKEN
| NUMBER_TOKEN | BOOLEAN_TOKEN | STRING_TOKEN;
JsonArray jsonArray = new JsonArray();
while (tokens.hasMore()) {
Token token = tokens.next();
TokenType tokenType = token.getTokenType();
String tokenValue = token.getValue();
switch (tokenType) {
case BEGIN_OBJECT:
checkExpectToken(tokenType, expectToken);
jsonArray.add(parseJsonObject());
expectToken = SEP_COMMA_TOKEN | END_ARRAY_TOKEN;
break;
case BEGIN_ARRAY:
checkExpectToken(tokenType, expectToken);
jsonArray.add(parseJsonArray());
expectToken = SEP_COMMA_TOKEN | END_ARRAY_TOKEN;
break;
case END_ARRAY:
checkExpectToken(tokenType, expectToken);
return jsonArray;
case NULL:
checkExpectToken(tokenType, expectToken);
jsonArray.add(null);
expectToken = SEP_COMMA_TOKEN | END_ARRAY_TOKEN;
break;
case NUMBER:
checkExpectToken(tokenType, expectToken);
if (tokenValue.contains(".") || tokenValue.contains("e") || tokenValue.contains("E")) {
jsonArray.add(Double.valueOf(tokenValue));
} else {
Long num = Long.valueOf(tokenValue);
if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE) {
jsonArray.add(num);
} else {
jsonArray.add(num.intValue());
}
}
expectToken = SEP_COMMA_TOKEN | END_ARRAY_TOKEN;
break;
case BOOLEAN:
checkExpectToken(tokenType, expectToken);
jsonArray.add(Boolean.valueOf(tokenValue));
expectToken = SEP_COMMA_TOKEN | END_ARRAY_TOKEN;
break;
case STRING:
checkExpectToken(tokenType, expectToken);
jsonArray.add(tokenValue);
expectToken = SEP_COMMA_TOKEN | END_ARRAY_TOKEN;
break;
case SEP_COMMA:
checkExpectToken(tokenType, expectToken);
expectToken = STRING_TOKEN | NULL_TOKEN | NUMBER_TOKEN | BOOLEAN_TOKEN
| BEGIN_ARRAY_TOKEN | BEGIN_OBJECT_TOKEN;
break;
case END_DOCUMENT:
checkExpectToken(tokenType, expectToken);
return jsonArray;
default:
throw new JsonParseException("Unexpected Token.");
}
}
throw new JsonParseException("Parse error, invalid Token.");
}
private void checkExpectToken(TokenType tokenType, int expectToken) {
if ((tokenType.getTokenCode() & expectToken) == 0) {
throw new JsonParseException("Parse error, invalid Token.");
}
}
}
复制代码
parseJsonObject 方法解析流程大致如下:
- 读取一个 Token,检查这个 Token 是否是其所期望的类型
- 如果是,更新期望的 Token 类型。否则,抛出异常,并退出
- 重复步骤1和2,直至所有的 Token 都解析完,或出现异常
上面的步骤并不复杂,但有可能不好理解。这里举个例子说明一下,有如下的 Token 序列:{、 id、 :、 1、 }
parseJsonObject 解析完 { Token 后,接下来它将期待 STRING 类型的 Token 或者 END_OBJECT 类型的 Token 出现。于是 parseJsonObject 读取了一个新的 Token,发现这个 Token 的类型是 STRING 类型,满足期望。于是 parseJsonObject 更新期望Token 类型为 SEL_COLON,即:。如此循环下去,直至Token 序列解析结束或者抛出异常退出。
上面的解析流程虽然不是很复杂,但在具体实现的过程中,还是需要注意一些细节问题。比如:
- 在 JSON 中,字符串既可以作为键,也可以作为值。作为键时,语法分析器期待下一个 Token 类型为 SEP_COLON。而作为值时,则期待下一个 Token 类型为 SEP_COMMA 或 END_OBJECT。所以这里要判断该字符串是作为键还是作为值,判断方法也比较简单,即判断上一个 Token 的类型即可。如果上一个 Token 是 SEP_COLON,即:,那么此处的字符串只能作为值了。否则,则只能
做为键。 - 对于整数类型的 Token 进行解析时,简单点处理,可以直接将该整数解析成 Long 类型。但考虑到空间占用问题,对于 [Integer.MIN_VALUE, Integer.MAX_VALUE] 范围内的整数来说,解析成Integer 更为合适,所以解析的过程中也需要注意一下。
其他类:
public class JsonUtils {
private static final char SPACE_CHAR = ' ';
private static final int INDENT_SIZE = 2;
private static int callDepth = 0;
public static String beautify(JsonObject jsonObject) {
StringBuilder sb = new StringBuilder();
sb.append(getIndentString());
sb.append("{");
callDepth++;
List<Map.Entry<String, Object>> keyValues = jsonObject.getAllKeyValue();
int size = keyValues.size();
for (int i = 0; i < size; i++) {
Map.Entry<String, Object> keyValue = keyValues.get(i);
String key = keyValue.getKey();
Object value = keyValue.getValue();
sb.append("\n");
sb.append(getIndentString());
sb.append(""");
sb.append(key);
sb.append(""");
sb.append(": ");
if (value instanceof JsonObject) {
sb.append("\n");
sb.append(beautify((JsonObject) value));
} else if (value instanceof JsonArray){
sb.append("\n");
sb.append(beautify((JsonArray) value));
} else if (value instanceof String) {
sb.append(""");
sb.append(value);
sb.append(""");
} else {
sb.append(value);
}
if (i < size - 1) {
sb.append(",");
}
}
callDepth--;
sb.append("\n");
sb.append(getIndentString());
sb.append("}");
return sb.toString();
}
public static String beautify(JsonArray jsonArray) {
StringBuilder sb = new StringBuilder();
sb.append(getIndentString());
sb.append("[");
callDepth++;
int size = jsonArray.size();
for (int i = 0; i < size; i++) {
sb.append("\n");
Object ele = jsonArray.get(i);
if (ele instanceof JsonObject) {
sb.append(beautify((JsonObject) ele));
} else if (ele instanceof JsonArray) {
sb.append(beautify((JsonArray) ele));
} else if (ele instanceof String) {
sb.append(getIndentString());
sb.append(""");
sb.append(ele);
sb.append(""");
} else {
sb.append(getIndentString());
sb.append(ele);
}
if (i < size - 1) {
sb.append(",");
}
}
callDepth--;
sb.append("\n");
sb.append(getIndentString());
sb.append("]");
return sb.toString();
}
private static String getIndentString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < callDepth * INDENT_SIZE; i++) {
sb.append(SPACE_CHAR);
}
return sb.toString();
}
}
复制代码
public class JSONParser {
/**
* 词法分析
*/
private Tokenizer tokenizer = new Tokenizer();
/**
* 语法解析
*/
private Parser parser = new Parser();
public Object fromJSON(String json) throws IOException {
CharReader charReader = new CharReader(new StringReader(json));
TokenList tokens = tokenizer.tokenize(charReader);
return parser.parse(tokens);
}
}
复制代码
关注木水小站 (zhangmushui.cn)和微信公众号【木水Code】,及时获取更多最新技术干货。