什么是JSON

JSON(JavaScript Object Notation)是指Javascript对象表示法。JSON是Javascript的一个子集,但JSON是一种独立的文本格式,用来存储和交换文本信息的语法。类似 XML。详细介绍可以参见介绍JSON

对于动态语言比如Javascript、Python来说,解析JSON属于小菜一碟;但对于静态强类型语言来说,解析JSON要费点功夫。尽管Java解析JSON比较麻烦,幸运的是,Java已经有很多强大的JSON解析器。

解析工具

本文使用org.json来解析JSON数据。org.json是非常轻量级的JSON解析器,不依赖于其他库。

JSON有两种数据类型:对象和数组。

  • 对象:通过花括号包含的键/值对
  • 数组:通过方括号包含的有序列表

这两种类型分别对应于org.json的JSONObject和JSONArray。

解析思路

需求:将配置文件中的JSON字符串转换为某个实例(比如自定义的Config类或Map)。

分析:

  1. 生成的“某个实例”到底是哪一个呢?可以增加一个输入参数Class<?>,表示生成实例的类型。
  2. 如果指定的类型不是Map,那么这个类型必须要包含JSON所有键所对应的域。比如JSON对象为{"name" : "Tim", "age" : 20},那么指定的类型必须包含String类型的name域和数值类型的age域。
  • 输入:1. JSON字符串或者代表JSON字符串的JSONObject;2. Class<?>表示生成实例的类型
  • 输出:对应的实例

第1步

如果定义一个函数json2obj来操作,函数的返回类型与Class<?>输入参数有关,不是固定的,所以可以采用泛型。那么json2obj的框架可以这么写:

1
2
3
public <T> T json2obj(JSONObject json, Class<T> Cls) {
...
}

第2步

现在可以想一个大致的步骤了:如果指定的类型是Map或其子类,那么就将JSON转化为Map实例(JSON的键/值对可以对应于Map的键/值对);否则,根据Class参数生成对应实例,将JSON中的键/值对填入到该实例对应的域中,这显然得用到反射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public <T> T json2obj(JSONObject json, Class<T> Cls) {
if (Map.class.isAssignableFrom(Cls)) {
T map = 解析json到map;
return map;
}
T res = Cls.newInstance();
Iterator<String> iter = json.keys();
while (iter.hasNext()) {
key = iter.next();
value = json.get(key);
设置res对应于key的域
}
return res
}

第3步

先将JSON转化为Map实例,这个比较容易:定义一个新的函数json2map,将JSON的键值对逐个填入到map实例中,并修改原来的json2obj函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private Map<String, Object> json2map(JSONObject json,
Class<Map<String, Object>> mapCls) {
Map<String, Object> map = mapCls.newInstance();
Iterator<String> iter = json.keys();
String key;
while (iter.hasNext()) {
key = iter.next();
map.put(key, json.get(key));
}
return map;
}
public <T> T json2obj(JSONObject json, Class<T> Cls) {
if (Map.class.isAssignableFrom(Cls)) {
return (T)json2map(json, (Class<Map<String, Object>>) Cls);
}
T res = Cls.newInstance();
Iterator<String> iter = json.keys();
while (iter.hasNext()) {
key = iter.next();
value = json.get(key);
设置res对应于key的域
}
return res
}

第4步

现在考虑普通实例的转换。如果key对应的value是普通类型,即字符串或数值,那么直接把值填写到对应的域中即可;如果value是对象(JSONObject),那么把value转化为对应域类型的实例,递归地调用json2obj函数。

接下来设置key对应的域:如果这个域是public的,直接设置;否则通过对应的setter方法设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
while (iter.hasNext()) {
key = iter.next();
value = json.get(key);
Field field = Cls.getDeclaredField(key);
// recursion
if (value instanceof JSONObject)
value = json2obj((JSONObject) value, field.getType());
if (Modifier.isPublic(field.getModifiers())) {
field.setAccessible(true);
field.set(res, value);
} else { // not accessible, use setter method
String methodName = "set" + Character.toUpperCase(key.charAt(0)) + key.substring(1);
Method setMethod = Cls.getMethod(methodName, field.getType());
setMethod.invoke(res, value);
}
}
...

总结

本文主要通过反射将JSON转化为Java实例,实例类型由输入参数Class<T> Cls决定,可以是Map类或者普通类。如果Java类是普通类,必须包含JSON对象所有键(key)所对应的同名同类型域,且可设置(public或者setter方法)。比如JSON包含名为”name”的key,那么Java类也应该包含name的域。JSON对象内也可以包含JSON对象,Java类内部也必须包含其他Java类型的域。程序通过递归的方式解析。

本文没有考虑JSON数组的转化。