JSON(JavaScript Object Notation) 作为一种轻量级的数据交换格式在项目中得到了广泛的应用,第三方的 JSON 开发工具包也就受到了广大开发人员的青睐,org.json 包便是其中之一,但是由于 org.json 含有 Licence 的限制,所以在一些项目中 json 包的替换成为一个急需解决的问题。本文基于这样的背景,引入第三方开源 json 包 json4j,剖析了 json4j 和 org.json 之间的不同点,提出了一个在实际项目中用 json4j 替换 org.json 包的转换方案。
引言
JSON 是基于 JavaScript(Standard ECMA-262 3rd Edition - December 1999)的一个子集。 它采用完全独立于语言的文本格式,但是也使用了类似于 C 语言家族的习惯(包括 C, C++, C#, Java, JavaScript, Perl, Python 等)。在 web 应用大行其道的今天,浏览器与服务器之间的数据交互已变成了家常便饭,JSON 和 XML 作为当今最流行的数据交互方式各有优势,在不同的运用场景下都有着广泛的运用。如今,随着大量异构系统整合项目的出现,JSON 和 XML 的使用早已超出了 web 应用的范围。JSON 的优势在于其是轻量型的数据格式,虽然其在可读性和扩展性方面稍逊 XML,但是其再数据表示方面的精简程度让在资源有限的环境中运行的系统是一个福音。
目前针对 Java 编程语言提供的第三方 JSON 开发工具包多种多样,org.json 和 json4j 是比较常见的两个。由于实际项目的要求,有时候需要对两者进行替换,这涉及到原有代码的改写和新代码的书写习惯,本文将通过对比这两者之间的不同,提供一个可操作的替换方案。
JSON 的数据格式
JSON 是基于纯文本的数据格式。JSON 的数据格式非常简单,您可以用 JSON 传输一个简单的 String,Number,Boolean,也可以传输一个数组,或者一个复杂的 Object 对象。
用 JSON 表示一个简单的 String “ abc ”,其格式为:"abc", String 的表示结构如图 1 所示:
图 1. String 的表示结构
一个整形或浮点数的表示为:123(整数),-1.2e10( 浮点数 ),其结构图见图 2:
图 2. Number 的表示结构
一个对象的表示方式为以“{”开头,“}”结尾,以“,”分隔的无序名值对。其结构图如下:
图 3. Object 的表示结构
一个数组的表示方式为以“[”开头,“]”结尾的一系列以“,”分隔的有序值。其结构如图 3 所示:
图 4. Array 的表示结构
一个值的可以是以引号括起来的 String 类型,一个 Number,一个对象,一个数组,true 或者 false,或者是 null。其结构如下图所示:
图 5. Value 的表示结构
第三方 JSON 包的功能
第三方的 JSON 包大都根据编程语言的特性,对我们常用的 JSON 操作进行了封装处理。由于在 JavaScript 中,开发人员可以根据 JavaScript 的语法方便的对 JSON 进行操作。但是在 JAVA,C++ 等面向对象开发语言中,SDK 没有包含类似的 API 供开发人员使用,这就是得 JSON 在这些编程语言的运用中显得繁琐,所以第三方的 JSON 开发工具包编应运而生了。
在这些第三方的开发工具包中,大都包含表 1 中的功能:
表 1. JSON 开发工具包的主要功能:
功能 | 对应于 org.json 包中的类 | 对应于 json4j 包中的类 |
---|---|---|
分词功能 | JSONTokener | Tokenizer |
JSON 文件转换成 JSONObject | JSONObject | JSONObject |
JSON 文件转换成 JSONObject | JSONArray | JSONArray |
JSONObjec 或 JSONArray 转换成 JSON 文件 | JSONWriter | JSONWriter |
JSON 异常处理 | JSONException | JSONException |
JSON 格式数据与 XML 格式数据相互转化 | JSONML,XML,XMLTokener | XML |
有了这些第三方包的支持,开发人员就可以方便的将 JSON 文件流转换成相应的 JSON 对象,然后利用第三发包提供的功能,便捷的获取数据,进行相应的逻辑。总而言之,工具包将解析 JSON 文件等繁琐重复的工作很好的封装起来,对外提供了良好的操作接口,使得开发人员能专注于本身的业务逻辑的开发上。
org.json 和 json4j 包的差异
org.json 包和 json4j 包在支持 JSON 操作的功能方面大致类似,但是一些 API 还是存在着较大的差别,在实际的项目中,如果使用到了这些 API,那么 json 包的切换就会遇到很到的麻烦。表 2 中列出了这两个 json 包 API 之间的一些差别。
表 2. org.json 包与 json4j 的差别
功能 | org.json | org.apache.wink.json4j |
---|---|---|
将 json 文件加载进来进行分词处理 | JSONTokener 类 | Tokenizer 类与之等效 |
为 String 值加引号, | JSONObject.quote() | 没有与之对应的方法实现 |
将 Number 类型转换成 String 类型 | JSONObject .numberToString() | 没有与之对应的方法实现,可以通过 Java 中的强制类型转换实现 |
将一个对象累加到一个 key 所指定的之中,即如果在一个 key 下已经存在了相应的值,那么该方法会将该 key 对应的值转换成数组,将新值累加进来 | JSONObject .accumulate( String, Object) | JSONObject.append() |
用 Java 数组来初始化一个 JSONArray 对象 | JSONArray(Object) | 没有与之对应的方法,但是我们可以通过 JSONArray(Collection) 来代替,通过先将 Java 数据转换成一个 Collection 对象,然后再调用此方法 |
读取 JSON 文件中以字符串形式表示的 Number 类型的值,如“12” | JSONObject.getInt() 可以正确的返回改字符串表示的数据 | JSONObject.getInt() 将会抛出不能转型的异常,即在 json4j 中,数值类型在 json 文件中不能够加引号 |
读取 JSON 文件中非字符串形式的 null 值,如 null | JSONObject.getXXX(), 返回 Java 中的 null | JSONObject.getXXX() 将会抛出异常 org.apache.wink.json4j.JSONException: The value for key: [xxx] was null. Object required. |
这两个包不但在 API 的提供方面有差异,对于相同的方法,其内部具体的实现也不完全一样,其不同点如表 3 所示:
表 3. org.json 和 json4j 在某些 API 上的差别
API 方法 | org.json | org.apache.wink.json4j |
---|---|---|
JSONObject .getDouble(key)/ .getBoolean(key)/ .getInt(key) | 即使实际的值保存为一个 String 或是一个对象,都能正确的转成相应的类型。 | 严格的执行类型匹配,例如你试图将一个非 Java Double 的类型转换成 Double 类型,将会抛出 Exception。 |
JSONObject.getLong(key) | 如果目标值查处 long 型的表示范围,则将值以字符串的形式返回 | 如果目标值超出 long 型范围,则会抛出异常 |
JSONObject 将 json 字符串转换成 json 对象 | 支持非标准格式的字符,如:“=>”,“;”,“(”以及隐式的 null 值 | 所有的非标准格式的 json 字符都不支持 |
JSONObject(Object),将一个 Java Bean 格式的对象转化成一个 JSONObject。 | 动态的决定父类是否需要被包含,如这个类是一个系统级别的类,那么它父类的方法则不会被包含。 | 所有的父类方法都会被包含 |
没有额外的键值对 | 总是会插入两个键值对: _classname: object.getClass().getName() _type: JavaClass | |
JSONObject.put( String k, Object v) | 简单将对象存储为 JSON 值 | 除了 String, Boolean, Number, JSONObject, JSONArray, JSONString 类型,所有其他类型的对象将会被当做 Bean 对象,解析进 JSONObject。 |
JSON 包替换解决方案
由于需要替换 JSON 包,所以我们不单单要做代码方面的修改,项目中所有涉及到应用 JSON 包的配置文件和 build 脚本也需要进行相应的修改。
配置文件方面的修改只需简单的替换即可,本文提供的解决方案主要将焦点集中在 Java code 方面的修改。详细的解决方案如下:
- 修改 JSON 文件。
将项目中的的 JSON 文件按照严格的 JSON 标准重新编写。由于各种第三方包对 JSON 格式的支持存在差异,但是都能够对 JSON 标准格式有着很好的支持,所以在项目的开发过程中,每个开发人员如果能够使用 JSON 标准格式来编写 JSON 文件,将会增加系统的对变动的适应性。
- import 语句的替换
import org.json.* ;
替换为:
import org.apache.wink.json4j.*
- 代码修改
- 为了在不修改 JSON 包的前提下使系统能够对新的 JSON 能够有很好的兼容性,通过在项目中根据自己的需要来创建一个 JSONUtils 将能够帮助我们很好的解决这个问题。图 5 为 JSONUtils 的一个类结构图:
图 6. JSONUtilsl 类
使用 org.json 时的 loadJson(InputStream) 的实现如下:
清单 1. 使用 org.json 时 parseJson 的实现
public static <T> T parseJson(InputStream is) throws Exception{ return (T) new JSONTokener(new InputStreamReader(is, "UTF-8")).nextValue(); }
而使用 json4j 时的实现则如下:
清单 2. 使用 json4j 时 parseJson 的实现
public static <T> T parseJson(File file) throws IOException { T obj = null; BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); if(isArray(file)){ obj = (T) new JSONArray(br); }else{ obj = (T) new JSONObject(br); } } catch (JSONException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } finally { br.close(): } return obj; }
通过使用泛型,我们可以方便将 JSONObject 和 JSONArray 一视同仁的从文件中加载进来,通过对 JSON 文件的分析动态的转换成正确的类型。
- 由于两种 json 包在 put(String K, Object v) 这个方法上差距很大,在 json4j 中,它将普通的 java 对象当成 Bean 对象来解析,并且添加 _classname: object.getClass().getName() 和 _type: JavaClass 这两个键值对,对原来系统的功能造成了毁灭性的影响。故我们在 JSONUtils 中重新实现该功能,代码如下所示:
清单 3. org.json 中将一个对象序列化成 json 文件
private static void writeValue(Object value, Writer writer,int indent, int indentFactor) throws IOException { if (value instanceof JSONString) { try { writer.write(((JSONString) value).toJSONString()); } catch (Exception ignore) { } } else if (value instanceof Number) { try { writer.write(JSONObject.numberToString((Number) value)); } catch (JSONException e) { throw new IOException(e.getLocalizedMessage(), e); } } else if (value instanceof Boolean) { writer.write(value.toString()); } else if (value instanceof JSONObject) { write((JSONObject) value, writer); } else if (value instanceof JSONArray) { write((JSONArray) value, writer); }else if (value.getClass().isArray()) { try { write(new JSONArray(value), writer); } catch (JSONException e) { throw new IOException(e.getLocalizedMessage(), e); } } if (value == null || JSONObject.NULL.equals(value)) { writer.write("null"); } else { writer.write(JSONObject.quote(value.toString())); } }
而这个功能对于 json4j 的版本我们只需将清单 3 中的最后一句 code 修改,这个修改有两种方式,一是修改 json4j 的源码,实现 quote 函数和 numberToString 函数,这显然不是一个上上之策,第二种方案是如清单 4 的修改,本文也是比 较推荐第二种方案,因为虽然是开源的工具包在项目的开发过程中修改工具包的源码是不太明智的做法。
清单 4 json4j 的 writeValue() 版本修改代码
writer.write(((Number) value).toString());// 处理 Number 类型 writer.write("\""+value.toString()+ "\"");// 处理普通的 Object 类型
在 org.json 中我们使用 JSONTokener 类在进行 JSON 文件到 JSON 对象的转换,在此类中,JSONTokener 通过对 JSON 文件分析,判断 JSON 文件中包含的是 JSONObject 还是 JSONArray 类型,进而进行相应的初始化操作,这一功能在 json4j 中被封装了起来,我们无法在外部调用,所以我们需要实现一个函数判断 JSON 文件中是 JSONAarry 还是 JSONObject,进而正确的进行初始化操作。该功能函数清单如下:
清单 5. isArray() 函数
public static boolean isArray(InputStream is){ boolean result = false; BufferedReader br = null; br = new BufferedReader(new InputStreamReader(is)); String line = ""; try { while((line = br.readLine()) != null && line.trim() == ""){ } if(line!= null && line.trim().startsWith("[")){ result = true; } } catch (IOException e) { e.printStackTrace(); }finally{ if(br != null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); }finally{ br = null; } } } return result; }
- 为了在不修改 JSON 包的前提下使系统能够对新的 JSON 能够有很好的兼容性,通过在项目中根据自己的需要来创建一个 JSONUtils 将能够帮助我们很好的解决这个问题。图 5 为 JSONUtils 的一个类结构图:
结束语
以上罗列了 org.json 和 json4j 这两种 JSON 开发工具包之间的不同点,并且根据在实际项目遇到的问题,给出了相应的解决方案,以供广大开发人员参考。两者之间还有可能还有很多的细微的不同之处,如果在实际的项目中遇到这种情况,就需要开人员细细研读两者的源码,发现两者之间的不同之处,相信问题便会迎刃而解。
参考资料
学习
- JSON 入门指南( developerWorks, 2008 年 8 月 22 日): 了解 JSON 的基本原理。
- JSON.org:访问 JSON Web 站点来进一步了解这种数据格式,包括到几种 JSON API 实现的链接。
- JSON4J:访问 JSON4J 的 API 文档。
- JSON 站点:有关 JSON 所有内容的集汇站,寻找 JSON 解析器和工具箱的最佳资源站点。
- 随时关注 developerWorks 技术活动和 网络广播。
- 访问 developerWorks Open source 专区获得丰富的 how-to 信息、工具和项目更新以及 最受欢迎的文章和教程,帮助您用开放源码技术进行开发,并将它们与 IBM 产品结合使用。
讨论
- 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。
- 加入 IBM 软件下载与技术交流群组,参与在线交流。