java json转换文本_解析Json文本——如何将Json文本转化为Java对象

Json是一种简单小巧的数据交换格式,在Web开发中获得了广泛应用。网络上有很多Json库,光用Java编写的就不下二十个之多。无论哪一个Json库都必须具有一个基本功能,就是把Json文本转换为用本语言表示的数据结构,本文就是介绍如何把Json文本一字符一字符的解析成Java对象。

如果要问解析Json需要哪些基础知识的话,计算机科班出身的读者立马就能想到大学时学过的编译原理这门课程。解析Json就是需要利用编译原理的知识,不过Json非常简单,解析它不必使用所有的编译技术,只要了解词法分析就可以了。不了解词法分析也不要紧,Json非常简单,不用词法分析也能解析。本文根据bantouyan-json库中解析Json文本的方法编写,不需要词法分析的基础。

在介绍怎样解析Json文本之前,我们先回顾一下Json的定义。如果要了解Json的详细定义可以查看RFC4627文档,或者www.json.org网站。本文只作简要的介绍。

39dfe2cd817ff79c571264e1cc50585b.gif

e72c934dabaf611e6ec060b26d4fc245.gif

b43fa61a79a1c0f75170101f97d82d48.gif

865e8dfee34ad8ed572ab55a0d5b3238.gif

462a1f05f1bf403aae94527570aff02a.gif

上面的五幅图像分别定义了Json的对象、数组、Value、字符串与数字。Json文本由一个Json对象或Json数组构成,无论是Json对象或者Json数组,还是Json字符串或数字,都是一个Json Value。

Json文本由构成Json的必要字符和额外的空白字符构成,解析它就要忽略额外的空白字符并将剩余部分转换为Java对象。本文按照bantouyan-json库提供的方法解析Json文本,介绍时做了一些适当的简化。

bantouyan-json库的解析功能由内部类JsonTextParser提供,JsonTextParser类的定义如下:

1 class JsonTextParser

2 {

3 private Reader reader;

4 private int ch = -1;

5

6 public JsonTextParser(String text)throws IOException

7 {

8 StringReader strReader = new StringReader(text);

9 this.reader = strReader;

10 next();

11 }

12

13 private void next() throws IOException

14 {

15 ch = reader.read();

16 }

17 }

JsonTextParser类的核心成员有reader和ch,reader存储要解析的字符串,ch表示当前被扫描的字符,方法next()每执行一次扫描一个字符,当ch的值是-1时表明字符串扫描结束。

在详细介绍解析算法之前,先介绍JsonTextParser类的几个关键方法,

boolean isBlankCharacter(int ch),判断字符ch是不是空白字符;void parseTailBlank(int endChar) 与void parseTailBlank(int endChar1, int endChar2),扫描并Json文本,在遇到终止字符endChar之前如果已经扫描完Json文本则抛出异常,扫描到第一个终止字符时退出该方法。

Json文本的解析从方法parse()开始(一个JsonTextParser对象方法parse只能被执行一次,这样设计也许并不合理,但它是内部类,不影响使用),其代码如下:

1 public Json parse() throws IOException, JsonException

2 {

3 Json json = null;

4

5 while(ch != -1)

6 {

7 if(ch == '{')

8 {

9 json = parseObject();

10 parseTailBlank(-1);

11 }

12 else if(ch == '[')

13 {

14 json = parseArray();

15 parseTailBlank(-1);

16 }

17 else if(! isBlankCharacter(ch))

18 {

19 //throw exception20 }

21

22 next();

23 }

24

25 return json;

26 }

parseTailBlank(-1)表示扫描完整个字符串前不允许出现非空白字符。Json文本只允许表示一个对象或数组,所以在字符“[”、“{”之前不允许出现非空白字符,扫描完第一个对象或数组(正常情况下只有一个)后也不允许再出现非空白字符。

方法parseJsonObject负责扫描并解析一个Json对象,调用该方法时正好扫描到Json对象的开始字符'{',得到Json对象后,扫描到对象的结束字符'}'的下一个字符时退出,parseJsonObject的代码如下:

private JsonObject parseObject() throws IOException, JsonException

{

JsonObject json = new JsonObject();

boolean needNextElement = false;

next(); //skip character '{'

while(ch != -1)

{

//还没有遇到JsonObject的子元素,扫描到'}'可直接结束 if(needNextElement == false && ch == '}') break;

if(isBlankCharacter(ch))

{

next(); //skip blank character }

else

{

String name = parseName();

Json value = parseValue('}');

parseTailBlank(',', '}');

json.set(name, value);

if (ch == '}') //子元素后是'}',JsonObject结束 {

break;

}

else //子元素后是',',需解析下一个子元素(Name Value对) {

next(); //skip character ',' needNextElement = true;

}

}

}

if(ch == '}')

{

next(); //skip character '}' }

else

{

//throw exception }

return json;

}

方法parseName 负责解析JsonObject子元素的Name部分(包括分界符':'),parseValue负责解析JsonObject子元素的Value部分。

方法parseJsonArray负责扫描并解析一个Json数组,调用该方法时正好扫描到Json数组的开始字符'[',得到Json数组后,扫描到数组的结束字符']'的下一个字符时退出,parseJsonArray的代码如下:

1 private JsonArray parseArray() throws IOException, JsonException

2 {

3 JsonArray json = new JsonArray();

4 boolean needNextElement = false;

5

6 next(); //skip character '['7

8 while(ch != -1)

9 {

10 //还没有遇到JsonArray的子元素,扫描到']'可直接结束11 if(needNextElement == false && ch == ']') break;

12

13 if (isBlankCharacter(ch))

14 {

15 next(); //skip blank character16 }

17 else

18 {

19 Json value = parseValue(']');

20 json.append(value);

21 parseTailBlank(',', ']');

22 if (ch == ']') //子元素后是']',数组结束23 {

24 break;

25 }

26 else //子元素后是',',需解析下一个子元素27 {

28 next(); //skip character ','29 needNextElement = true;

30 }

31 }

32 }

33

34 if(ch == ']')

35 {

36 next(); //skip character ']'37 }

38 else

39 {

40 //throw exception41 }

42

43 return json;

44 }

方法parseValue负责解析Json数组的子元素。

方法parseName负责扫描并解析Json对象子元素的Name部分,调用该方法时正好扫描到Name部分的第一个字符(非空白字符),得到Name后,扫描到Name部分的结束字符':'的下一个字符时退出,parseName的代码如下:

1 private String parseName() throws IOException, JsonException

2 {

3 String name = null;

4 name = parseString(ch);

5 parseTailBlank(':');

6

7 if(ch == ':') //Name部分正常结束8 {

9 next(); //skip character ':'10 }

11 else

12 {

13 //throw exception14 }

15

16 return name;

17 }

JsonObject子元素的Name部分就是一个字符串,方法parseName扫描这个字符串和随后的结束符':' 。

parseJsonObject与parseJsonArray都调用到方法parseValue, 方法parseValue负责解析一个Json Value。在Json中,Value可以是Json对象、Json数组、Json字符串、Json数字、true、false或null,它出现在Json对象子元素的Value部分和Json数组的子元素处,把这两处用到的代码归纳在一起,就是方法parseValue。调用parseValue时正好扫描到Value部分的第一个字符(可以是Value部分的第一个前导空白字符),扫描完Value部分(不包括后缀空白字符)后遇到下一个字符时退出,parseValue的代码如下:

1 private Json parseValue(int endChar) throws IOException, JsonException

2 {

3 Json json = null;

4

5 while(ch != -1)

6 {

7 if(isBlankCharacter(ch))

8 {

9 next(); //skip blank character10 }

11 else if(ch == '{')

12 {

13 json = parseObject();

14 break;

15 }

16 else if(ch == '[')

17 {

18 json = parseArray();

19 break;

20 }

21 else if(ch == 't') //parse true22 {

23 json = parseJsonConstant("true", trueAry, endChar);

24 //trueAry是一个私有静态数组,内容是{'t', 'r', 'u', 'e'}25 break;

26 }

27 else if(ch == 'f') //parse false28 {

29 json = parseJsonConstant("false", falseAry, endChar);

30 //falseAry是一个私有静态数组,内容是{'f', 'a', 'l', 's', 'e'}31 break;

32 }

33 else if(ch == 'n') //parse null34 {

35 json = parseJsonConstant("null", nullAry, endChar);

36 //nullAry是一个私有静态数组,内容是{'n', 'u', 'l', 'l'}37 break;

38 }

39 else if(ch == '\"')

40 {

41 String str = parseString();

42 json = new JsonPrimitive(str);

43 break;

44 }

45 else if(ch == '-' || (ch >= '0' && ch<= '9'))

46 {

47 Number num = parseNumber(endChar);

48 json = new JsonPrimitive(num);

49 break;

50 }

51 else

52 {

53 //throw exception54 }

55 }

56

57 return json;

58 }

Value部分第一个非空白字符如果是'{'则Value是一个Json对象,如果是字符'[' 则Value是一个Json数组,如果是字符'"'则肯定是Json字符串,如果是字符'-'或字符'0'到'9'则肯定是Json数字,如果是字符't'则肯定是Json常量true,如果是字符'f'则肯定是常量false,如果是字符'n'则肯定是常量null,否则肯定Json文本格式有误,因而无法解析。

parseJsonObject与parseJsonArray前面都已经给予了介绍,下面介绍剩余的方法。

方法parseString负责扫描并解析一个Json字符串,调用该方法时正好扫描到Json字符串的开始字符'"',扫描完Json字符串后,扫描到字符串的结束字符'"'的下一个字符时退出,parseString的代码如下:

1 private String parseString() throws IOException, JsonException

2 {

3 StringBuilder build = new StringBuilder();

4 next(); //skip quatorChar "5

6 while(ch != -1)

7 {

8 if(ch == '"') break;

9

10 if(ch < 0x0020)

11 {

12 //throw exception13 }

14 if(ch == '\\')

15 {

16 next();

17 switch(ch)

18 {

19 case '\"':

20 ch = '\"';

21 break;

22 case '\'':

23 ch = '\'';

24 break;

25 case '\\':

26 ch = '\\';

27 break;

28 case '/':

29 ch = '/';

30 break;

31 case 'b':

32 ch = '\b';

33 break;

34 case 'f':

35 ch = '\f';

36 break;

37 case 'n':

38 ch = '\n';

39 break;

40 case 'r':

41 ch = '\r';

42 break;

43 case 't':

44 ch = '\t';

45 break;

46 case 'u':

47 for(int i=0; i<4; i++)

48 {

49 next();

50 if((ch >='0' && ch <='9') || (ch >= 'a' && ch <='f')

51 || (ch>= 'A' && ch <= 'F'))

52 {

53 unicodeChar[i] = (char)ch;

54 //unicodeChar是一个私有字符数组,长度为455 }

56 else

57 {

58 //throw exception59 }

60

61 }

62 ch = Integer.parseInt(new String(unicodeChar), 16);

63 break;

64 default:

65 //throw exception66 }

67 }

68 build.append((char)ch);

69

70 next();

71 }

72

73 if(ch == '"')

74 {

75 next(); //skip quator char76 }

77 else

78 {

79 //throw exception80 }

81

82 return build.toString();

83 }

解析Json字符串的关键在于处理转义字符序列,要能够恰当的将转义字符序列转换为对应的字符,遇到非法转义字符时要报告错误。

Json中的数字与通常意义上的数字字面量有点不同,它不允许有不必要的前导零,也不允许有前导符号'+'。方法parseNumber负责扫描并解析一个Json数子,调用该方法时正好扫描到Json数字的开始字符,扫描完Json数字后,扫描到数字的下一个字符时退出,parseString的代码如下:

1 private Number parseNumber(int endChar) throws IOException, JsonException

2 {

3 StringBuilder build = new StringBuilder();

4 boolean isInt = true;

5

6 //parse minus sign7 if(ch == '-')

8 {

9 build.append((char)ch);

10 next();

11 }

12

13 //parse integer part14 if(ch == '0') //begin with 015 {

16 build.append((char)ch);

17 next();

18 if(ch >= '0' && ch <= '9')

19 {

20 //throw exception21 }

22 }

23 else if(ch > '0' && ch <= '9') //begin with 1..924 {

25 build.append((char)ch);

26 next();

27

28 while(ch != -1)

29 {

30 if(ch >= '0' && ch <= '9')

31 {

32 build.append((char)ch);

33 }

34 else

35 {

36 break;

37 }

38 next();

39 }

40 }

41 else

42 {

43 //throw exception44 }

45

46 //parse fraction47 if(ch == '.')

48 {

49 build.append((char)ch);

50 isInt = false;

51 next(); //skip character '.'52

53 if(ch>='0' && ch<='9')

54 {

55 while(ch != -1)

56 {

57 if(ch>='0' && ch<='9')

58 {

59 build.append((char)ch);

60 }

61 else

62 {

63 break;

64 }

65 next();

66 }

67 }

68 else

69 {

70 //throw exception71 }

72 }

73

74 //parse exponent75 if(ch == 'e' || ch == 'E')

76 {

77 build.append((char)ch);

78 isInt = false;

79 next(); //skip character e80 81 //parse plus or minus sign82 if(ch == '+' || ch == '-')

83 {

84 build.append((char)ch);

85 next();

86 }

87

88 if(ch>='0' && ch<='9')

89 {

90 while(ch != -1)

91 {

92 if(ch>='0' && ch<='9')

93 {

94 build.append((char)ch);

95 }

96 else

97 {

98 break;

99 }

100 next();

101 }

102 }

103 else

104 {

105 //throw exception106 }

107 }

108 if(ch != ',' && ch != endChar && !isBlankCharacter(ch))

109 {

110 //throw exception111 }

112

113 String numStr = build.toString();

114 try

115 {

116 if(isInt)

117 {

118 return Long.parseLong(numStr);

119 }

120 else

121 {

122 return Double.parseDouble(numStr);

123 }

124 }

125 catch (NumberFormatException e)

126 {

127 //throw exception128 }

129 }

解析Json数字时使用一个StringBuilder存储表示数字的字符串,一边扫描一遍检查数字格式,最后根据扫描到的字符串能否转换为整数决定是用Long.parseLong(String)解析还是用Double.parseDouble(String)解析。

方法parseJsonConstant()负责解析Json常量true、false和null,调用时正好扫描到常量的第一个字符,退出时扫描到常量的下一个字符时退出,如果常量的下一个字符既不是分界符也不是是空白字符,那么认为所扫描到的不是一个Json常量处理。parseJsonConstant()的代码如下:

1 private JsonPrimitive parseJsonConstant(String constName, int[] constAry, int endChar)

2 throws IOException, JsonException

3 {

4 JsonPrimitive json = null;

5

6 for(int i=0; i

7 {

8 if(ch != constAry[i])

9 {

10 //throw exception11 }

12

13 next();

14 }

15 if(ch != ',' && ch != endChar && !isBlankCharacter(ch))

16 {

17 //throw exception18 }

19

20 json = (constAry == trueAry)? Json.trueJson:

21 (constAry == falseAry)? Json.falseJson: Json.nullJson;

22

23 return json;

24 }

参数endChar表示Json常量后除','外允许跟的另外一个分界符,参数constAry用于比较所扫描的字符,参数constName用来报告更直观的异常。

本文介绍的算法对字符串只进行一次扫描,不需要回溯,因此时间复杂度为O(n)。一遍扫描的好处是不用回溯,算法简单,坏处是如果被解析的字符串格式不正确,那么只能报告所发现的第一个错误。好在大多数情况下本解析的Json文本都是正确的,而且代码一般也不负责修复Json文本的错误,所以采用一次扫描的策略是可行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值