什么是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)。
分析:
- 生成的“某个实例”到底是哪一个呢?可以增加一个输入参数
Class<?>
,表示生成实例的类型。
- 如果指定的类型不是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数组的转化。