bantouyan-json库是用来解析与编码Json数据的Java库,该库按照Json标准RFC4627编写,能够实现字符串与Json实例的相互转换,可以读取Reader得到Json实例,或将Json实例写入到Writer,还能将Collection与Map对象转换为Json实例。
RFC4627定义了Json的六种类型,分别是Array、Object、String、Number、Boolean(常量true与false)与Null(常量null)。在bantouyan-json库中,这些类型有枚举类型JsonType定义,其中Number被拆分为Integer与Float,非别表示整数与浮点数。这些类型的对应关系如下表所示:
RFC类型 | JsonType | Class | 实际存储类型 | 注释 |
Array | ARRAY | JsonArray | ArrayList | |
Object | OBJECT | JsonObject | HashMap | |
String | STRING | JsonPrimitive | java.lang.String | |
Number | INTEGER | JsonPrimitive | java.lang.Long | |
Number | FLOAT | JsonPrimitive | java.lang.Double | 不包括Infinity与NaN |
Boolean | BOOLEAN | JsonPrimitive | java.lang.Boolean | 对应常量true与false |
Null | NULL | JsonPrimitive | java.lang.String | 对应常量null |
INTEGER使用类型long存储整数,FLOAT使用类型double存储浮点数,但是,Java浮点数常量正负INFINITY与NaN存储为STRING类型,因为按照RFC4627这三个常量的字面量不符合Number的定义。
在bantouyan-json库中,JsonArray、JsonObject与JsonPrimitive有一个共同的超类Json,Json是一个抽象类,定义了这些类的一些共同特征,如Type、子元素的个数等,还提供了一些静态方法用来生成Json实例。
在解析、处理、编码Json的过程中,总会发生这样或那样的错误,为此,定义了JsonException异常。如果在处理Json实例过程中产生了异常,如存取类型不正确,Json实例内出现了循环引用等,或在解析String、Reader或Java Map、 Collection为Json实例过程中产生了错误,都会抛出JsonException异常。JsonException异常都属于RuntimeException,不必要时可以不予捕获。
在解析Collection或Map为Json实例的过程中,如果遇到普通的Java Class,bantouyan-json库就无法确定该如何解析这些普通类的对象。为此,json库中又定义了一个名为Jsonable的接口,该接口只有一个返回Json实例的方法generateJson()。故当解析Collection或Map时,如果遇到Jsonable的实例,就会调用所继承的generateJson()方法生成Json实例。
除了接口Jsonable外,bantouyan-json库还定义了另外一个接口JsonParser,也用于将Collection或Map的转换。与Jsonable只负责把自己转换为Json实例不同,JsonParser负责把其他的Java对象转换为Json实例。当我们需要把非自己编写的Java类转换为Json实例时,JsonParser非常有用。
特别警告:Json实例内部不允许出现循环引用,即Json实例内部,任何一个元素(包括顶层实例),都不能被其子元素或子元素的子元素引用,否则会引起一些方法出现异常或错误。因为从现实意义上讲,Json内部不会出现循环引用,但编写代码时可能有意无意的制造出有循环引用的实例。
解析Json——Json类的静态方法
要使用bantouyan-json库解析Json数据,可以使用类Json。类Json是JsonArray、JsonObject和JsonPrimitive的基类,它有四个静态方法用于解析Json数据,这四个方法分别是parseJsonText、parseJsonReader、parseJavaMap、parseJavaCollection,下面依次介绍。
一、parseJsonText
要将一个表示Json文本的字符串解析成一个Json实例,就要调用方法Json类的静态方法parseJsonText,示例代码如下:
- import com.bantouyan.json.JsonObject;
- public class TestBtyJson
- {
- public static void main(String[] args)
- {
- String jsonText = "{a: \"AA\", b: \"BB\", c: 23, d: true, e: null}";
- JsonObject jobj = (JsonObject)Json.parseJsonText(jsonText);
- System.out.println(jobj.getString("b"));
- }
- }
- //输出: BB
parseJsonText返回一个Json类实例,但在实际使用过程中,我们更常使用的类是JsonArray和JsonObject,你可以用instanceof操作符或者Json类的实例方法getType来确定返回实例的类型。通常情况下,我们知道所解析的字符串内的Json文本到底是表示JsonArray还是JsonObject,即我们知道返回的Json实例的类型,所以我们可以直接使用强制类型转换。
如果我们传给方法parseJsonText的文本既不能表示成一个JsonArray,也不能表示成一个JsonObject,那么这个方法将抛出一个JsonException异常。这是一个runtimeException,没有必要时可以不予捕获,如果你不能保证所解析的Json文本格式正确,那么最好捕获这个异常并加以处理。捕获异常的示例代码如下:
- import com.bantouyan.json.*;
- public class TestBtyJson
- {
- public static void main(String[] args)
- {
- String jsonText = "{a \"AA\", b: \"BB\", c: 23, d: true, e: null}";
- JsonObject jobj = null;
- try
- {
- jobj = (JsonObject)Json.parseJsonText(jsonText);
- }
- catch (JsonException e)
- {
- System.out.println(e.getMessage());
- }
- }
- }
- //输出: Non-blank character found at position 3 before ':'.
这个例子中Json Object的第一个子元素的Name与Value之间少了分隔符“:”,导致抛出异常JsonException,异常的Message解释了出现异常的原因:在“:”之前发现了非空白字符。
二、parseJsonReader
如果待解析的文本来自一个Reader对象,那么可以调用Json类的静态方法parseJsonReader,这个方法除了会抛出JsonException异常外,还会抛出IOException异常。示例代码如下:
- import java.io.IOException;
- import com.bantouyan.json.*;
- public class TestBtyJson
- {
- public static void main(String[] args)
- {
- Reader reader = new ......
- JsonObject jobj = null;
- try
- {
- jobj = (JsonObject)Json.parseJsonReader(reader);
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- catch (JsonException e)
- {
- System.out.println(e.getMessage());
- }
- }
- }
parserJsonReader要求被解析的JsonReader内包含一个完整的Json字符串,不允许Json字符串前后有其他非空白内容,否则会抛出异常,而且,读取Reader导致的异常IOException必须在代码中予以处理。
三、parseJavaCollection
如果要将一个Java Collection对象转换为一个JsonArray实例,就要调用方法parseJavaCollection,但要保证Collection对象内部没有循环引用并且所有的子元素都能解析,否则会抛出JsonException异常。示例代码如下:
- import java.util.*;
- import com.bantouyan.json.*;
- public class TestBtyJson
- {
- public static void main(String[] args)
- {
- ArrayList<Object> list = new ArrayList<Object>();
- //list.add(list);
- //list.add(new Timer());
- JsonArray jary = null;
- try
- {
- jary = Json.parseJavaCollection(list);
- }
- catch (JsonException e)
- {
- System.out.println(e.getMessage());
- }
- }
- }
- //(第一行注释)输出:Java colloection is referenced again.
- //(第二行注释)输出:Cannot parse value: java.util.Timer@60aeb0 to json instance.
例子中的ArrayList经过解析后将得到一个空的JsonArray实例。如果把代码中的第一行注释去掉,那么将抛出由循环引用导致的异常,如果把第二行注释去掉,那么将抛出由无法解析导致的异常。
四、parseJavaMap
parseJavaMap与parseJavaCollection相似,只是把一个Java Map对象解析成JsonObject实例,同样要求Map对象内部没有循环引用并且所有的元素都能解析,否则会抛出JsonException异常。示例代码如下:
- import java.util.*;
- import com.bantouyan.json.*;
- public class TestBtyJson
- {
- public static void main(String[] args)
- {
- HashMap<Object, Object> map = new HashMap<Object, Object>();
- //map.put("self", map);
- //map.put("a", new Timer());
- JsonObject jobj = null;
- try
- {
- jobj = Json.parseJavaMap(map);
- }
- catch (JsonException e)
- {
- System.out.println(e.getMessage());
- }
- }
- }
- //(第一行注释)输出:Java map is referenced again.
- //(第二行注释)输出:Cannot parse value: java.util.Timer@60aeb0 to json instance.
这个例子与上一个类似,map对象解析后得到一个空的JsonObject实例,如果把代码中的第一行注释去掉,那么将抛出由循环引用导致的异常,如果把第二行注释去掉,那么将抛出由无法解析导致的异常。
五、JsonParser
默认情况下,parseJavaCollection与parseJavaMap只能处理一些简单的类型,如String、Number、Boolean等,如果要处理普通的Java类,就要为这些类实现Jsonable接口。但是这有局限性,例如我们要处理的类不是我们自己编写的而是来自其他包,这时候再使用Jsonable就很不方便。为了解决这种不便,bantouyan-json库还提供了另外一个接口JsonParser,该接口用在parseJavaCollection与parseJavaMap的重载版本parseJavaCollection(Collection, Jsonparser)与parseJavaMap(Map, Jsonparser)中。
Jsonparser有四个方法,canToName用于判断Java对象是否可以转换为JsonObject子元素的Name,changToName用于将Java对象转换为JsonObject子元素的Name,canToJson用于判断Java对象是否可以转换为Json实例,changeToJson用于将Java对象转换为Json实例。需要注意的是,只有通过canToJson(canToName)的Java对象,即返回true,才会在parseJavaCollection(Collection, Jsonparser)与parseJavaMap(Map, Jsonparser)中用changeToJson(changeToName)处理,如果通不过,即返回false,则按默认规则处理。
下面是一个例子:
- import com.bantouyan.json.*;
- public class StringParser implements JsonParser
- {
- public boolean canToName(Object obj)
- {
- if(obj instanceof String)
- return ((String)obj).startsWith("*");
- else
- return false;
- }
- public String changeToName(Object obj) throws JsonException
- {
- String str = (String)obj;
- return "Name_" + str.substring(1);
- }
- public boolean canToJson(Object obj)
- {
- if(obj instanceof String)
- return ((String)obj).startsWith("#");
- else
- return false;
- }
- public Json changeToJson(Object obj) throws JsonException
- {
- String str = (String)obj;
- return new JsonPrimitive("Value_" + str.substring(1));
- }
- }
- import java.util.*;
- import com.bantouyan.json.*;
- public class TestBtyJson
- {
- public static void main(String[] args)
- {
- HashMap<Object, Object> map = new HashMap<Object, Object>();
- map.put("na", "#va");
- map.put("*b", "vb");
- ArrayList<Object> list = new ArrayList<Object>();
- list.add("eA");
- list.add("#eb");
- list.add(map);
- JsonParser parser = new StringParser();
- JsonArray jaryA = Json.parseJavaCollection(list);
- System.out.println(jaryA);
- JsonArray jaryB = Json.parseJavaCollection(list, parser);
- System.out.println(jaryB);
- }
- }
- //输出:["eA","#eb",{"*b":"vb","na":"#va"}]
- //输出:["eA","Value_eb",{"Name_b":"vb","na":"Value_va"}]
例子中的JsonParse对于以*开头的Name,会转换为以Name_开头, 对于以#开头的Value,会转换为以Value_开头的字符串型Json实例。
解析Json——Json类的实例方法
作为所有Json实例的基类Json定义了操作Json实例的通用方法,下面将一一介绍。
一、输出Json文本
要把Json实例转换为字符串,可以调用Json类的方法generateJsonText,这个方法有两个重载版本,带参数的与不带参数的。带参数的generateJsonText(boolean)让你自己决定JsonObject子元素的Name部分是否用引号括起来,不带参数的版本相当于参数为false的情况,只是转换失败时仅返回一个空指针而不抛出异常。
Json类还重写toString方法,toString方法等同于不带参数的generateJsonText()。
在Servlet编程中,直接把Json文本输出到Response的Writer对象更方便。为此,Json类实现了outputToWriter方法,这个方法也有两个重载版本,分别是outputToWriter(PrintWriter, boolean)与outputToWriter(PrintWriter, boolean)。outputToWriter方法的第二个参数与generateJsonText方法的参数意义一致,第一个参数的区别是用PrinteWriter时不会抛出必须捕获的IOException,而用Writer时必须处理IOException。
二、Json实例的通用方法
Json实例通用的方法有下面几个:
-
-
- isEmtpy:判断Json实例是否包含子元素。
- count:Json实例子元素的个数。
- clear:清除Json实例所有的子元素。
- getType:返回Json实例的类型。
- existsCircle:Json实例内是否存在循环引用,如果存在会导致输出文本异常。
-
三、Json实例的相等性判断
Json类重写了equals方法,只要两个Json实例所表示的数据一致(即类型一致、子元素的个数一致且对应相等,对于JsonPrimitive来讲是自身的值相等)就返回true,而不管在内存中的映像是否一致。
Json类也重写了作为与equals配对使用的方法hashCode,只要equals方法返回true,hashCode肯定返回相同的值。
四、克隆Json实例
Json类也重写了方法clone,能够实现Json实例的深度克隆,即无论如何修改被克隆出的Json实例(即使修改子元素的子元素),都不会影响原Json实例的值。
解析Json——操纵JsonObject
Json对象是Name Value对(即子元素)的无序集合,相当于一个Map对象。JsonObject类是bantouyan-json库对Json对象的抽象,提供操纵Json对象的各种方法。本文就介绍如何操纵JsonObject类。
一、创建JsonObject实例
创建JsonObject实例有两类方法,一是利用超类Json的静态方法parseJsonText、parseJsonReader与parseJavaMap获取JsonObject实例,二是直接利用JsonObject类的构造方法创建JsonObject实例。
根据传入的参数不同,parseJsonText返回一个JsonObject实例或JsonArray实例,利用parseJsonText方法的示例代码如下:
- String jsonText = "{'name1': 'value1', 'name2': 'value2'}";
- JsonObject jobj = (JsonObject)Json.parseJsonText(jsonText);
parseJsonText返回的是一个Json类变量,所以要使用强制类型转换。
parseJsonReader负责从Reader类型参数内读取Json文本流,然后转换为Json实例,与parseJsonText一样,返回值需要强制类型转换。
方法parseJavaMap直接返回JsonObject变量,不用类型转换,示例代码如下:
- HashMap<Object, Object> map = new HashMap<Object, Object>();
- map.put("nameA", "valueA");
- map.put("nameB", "valueB");
- JsonObject jobj = Json.parseJavaMap(map);
如果Map内有复杂的对象需要解析,可以用parseJavaMap的重载版本parseJavaMap(Map, JsonParser)来处理(JsonParser的使用参考解析Json——Json类的静态方法的第五部分)。
JsonObject类的构造函数有四个重载版本:JsonObject()、JsonObject(int)、JsonObject(Map)与JsonObject(Map, JsonParser)。不带参数与带整型参数的重载版本都构造一个空的JsonObject实例,所不同的是带整型参数的重载版本能够指定JsonObject初始容量的大小,以避免不必要的重新分配内存。重载版本JsonObject(Map)与JsonObject(Map, JsonParser)的使用类似于Json类的静态方法parseJavaMap。
二、给JsonObject添加子元素
给JsonObject实例添加子元素调用方法add或addAll。bantouyan-json库规定,方法add与addAll都不能添加Name为null的子元素,也不能添加与已有子元素Name相同的子元素,否则会抛出异常。
方法add有七种重载版本,方法addAll有三种重载版本,分别使用于不同的情况。
三、变更JsonObject子元素的Value
要改变JsonObject子元素的Value可以调用方法set与setAll。这两个方法都忽略Name为null的子元素,如果存在Name相同的子元素,则更改这个子元素的Value,否则添加一个新的子元素。
方法set也有七种重载版本,addAll有三种重载版本,分别适用于不同的情况。
四、获取与检测JsonObject子元素
JsonObject的每个子元素的Value都是一个Json实例,可以用方法get(String)获取这个实例。至于这个实例的类型,除可以调用方法getType()获得外,还可以通过JsonObject对方法getType的重载版本getType(String)获取(String为子元素的Name)。
利用方法getString(String)可以获取指定Name的子元素Value的字符串值,如果子元素的Value是JsonPrimitive实例,则返回这个实例值对应的字符串(不带引号与转义符),否则返回对应的标准Json文本。
如果想获取子元素的Value所对应的boolean、double、long、JsonArray与JsonObject类型的值,则可以分别调用方法getBoolean(String)、getDouble(String)、getLong(String)、getJsonArray(String)与getJsonObject(String)。与getString方法不一样的是当子元素的Value无法转换为相应的类型时会抛出异常。方法canToBoolean(String)、canToDouble(String)、canToLong(String)、canToJsonArray(String)与canToJsonObject(String)可以检测是否可以转换为对应的类型。
以特定的类型获取子元素的Value时,方法canToXXX返回true并不表明子元素的Value就是所测试的类型。根据bantouyan-json库设计,如果子元素的Value的类型是INTEGER,则可以得到对应的double类型的值,如果类型时String,对于部分Value,可以得到对应的boolean、long、double类型的值。
五、确定JsonObject子元素是否存在
在获取子元素前可能无法确定JsonObject是否包含指定Name的子元素,要确定子元素存在,请调用方法containsName(String)。
六、删除JsonObject子元素
删除JsonObject的子元素调用方法remove(String),参数为要删除的子元素的Name。
七、获取JsonObject子元素相关的集合
与Map一样,JsonObject也是由无序的Name Value对构成,为此,JsonObject实现了三个方法用于获取子元素相关的集合。
entrySet(),返回由子元素的Name Value对构成的集合。
nameSet(),返回由子元素的Name构成的集合。
values(),返回由子元素的Value构成的集合。
解析Json——操纵JsonArray
Json数组是子元素的有序集合,每个子元素都有一个下标,可以根据下标操纵Json数组的子元素。类JsonArray是bantouyan-json库对Json数组的抽象,提供操纵Json数组的各种方法。本文就介绍如何操纵JsonArray。
一、创建JsonArray实例
创建JsonArray实例有两类方法,一是利用超类Json的静态方法parseJsonText、parseJsonReader与parseJavaCollection获取JsonArray实例,二是直接利用JsonArray类的构造方法创建JsonArray实例。
根据传入的参数不同,parseJsonText返回一个JsonObject实例或JsonArray实例,利用parseJsonText方法的示例代码如下:
- String jsonText = "['value1', 'value2', true, null]";
- JsonArray jary = (JsonArray)Json.parseJsonText(jsonText);
parseJsonText返回的是一个Json类变量,所以要使用强制类型转换。
parseJsonReader负责从Reader类型参数内读取Json文本流,然后转换为Json实例,与parseJsonText一样,返回值需要强制类型转换。
方法parseJavaCollection直接返回JsonArray变量,不用类型转换,示例代码如下:
- ArrayList<Object> collection = new ArrayList<Object>();
- collection.add("value1");
- collection.add(true);
- collection.add(30);
- collection.add(null);
- JsonArray jary = Json.parseJavaCollection(collection);
如果Collection内有复杂的对象需要解析,可以用parseJavaCollection的重载版本parseJavaCollection(Collection, JsonParser)来处理(JsonParser的使用参考解析Json——Json类的静态方法的第五部分)。
JsonArray类的构造函数有四个重载版本:JsonArray()、JsonArray(int)、JsonArray(Collection)与JsonArray(Collection, JsonParser)。不带参数与带整型参数的重载版本都构造一个空的JsonArray实例,所不同的是带整型参数的重载版本能够指定JsonArray初始容量的大小,以避免不必要的重新分配内存。重载版本JsonArray(Collection)与JsonArray(Collection, JsonParser)的使用类似于Json类的静态方法parseJavaCollection。
二、给JsonArray添加子元素
JsonArray是子元素的有序集合,所以给JsonArray添加子元素应该指明子元素的位置,方法insert、insertAll、append、appendAll都可以添加子元素到JsonArray,不同的是方法insert与insertAll可以在任意位置添加子元素,方法append与appendAll只能在JsonArray的末尾追加子元素。
方法insert与append都有七种重载版本,方法insertAll与appendAll都有三种重载版本,分别适用于不同的情况。
三、修改JsonArray的子元素
方法set用来修改JsonArray子元素的值,有七种重载版本,每种版本的第一个参数都是被修改的子元素的下标。
四、获取与检测JsonArray子元素
JsonArray的每个子元素都是一个Json实例,可以用方法get(int)获取这个实例。至于这个实例的类型,除可以调用方法getType()获得外,还可以通过JsonArray对方法getType的重载版本getType(int)获取(参数为子元素的下标)。
利用方法getString(int)可以获取指定下标子元素的字符串值,如果子元素是JsonPrimitive实例,则返回这个实例值对应的字符串(不带引号与转义符),否则返回对应的标准Json文本。
如果想获取子元素所对应的boolean、double、long、JsonArray与JsonObject类型的值,则可以分别调用方法getBoolean(int)、getDouble(int)、getLong(int)、getJsonArray(int)与getJsonObject(int)。与getString方法不一样的是当子元素无法转换为相应的类型时会抛出异常。方法canToBoolean(int)、canToDouble(int)、canToLong(int)、canToJsonArray(int)与canToJsonObject(int)可以检测是否可以转换为对应的类型。
以特定的类型获取子元素时,方法canToXXX返回true并不表明子元素就是所测试的类型。根据bantouyan-json库的设计,如果子元素的类型是INTEGER,则可以得到对应的double类型的值,如果类型时String,对于部分情况,可以得到对应的boolean、long或double类型的值。
五、删除JsonArray子元素
删除JsonArray的子元素调用方法remove(int),参数为要删除的子元素的下标。