JSON是极常用的一种数据结构,跨语言,且可以与String非常友好地互相转化,尤其在客户端与服务器的http接口通讯中广泛使用。本文从JSON与String相互转化的角度介绍一下Android下JSON的源代码。源代码及测试手机环境为Android 6.0.1。
1.从一段测试代码开始
测试代码:
private void testJson2() {
try {
JSONObject sonJsonObject = new JSONObject();
sonJsonObject.put("sonkey1", "sonvalue1");
sonJsonObject.put("sonkey2", "sonvalue2");
JSONObject jsonObject1 = new JSONObject();
JSONObject jsonObject2 = new JSONObject();
jsonObject1.put("key1", "value1");
jsonObject1.put("son", sonJsonObject);
jsonObject2.put("key1", "value1");
jsonObject2.put("son", sonJsonObject.toString());
Log.i("TESTJSON", "json1 :\n" + jsonObject1.toString());
Log.i("TESTJSON", "json2 :\n" + jsonObject2.toString());
JSONObject dad1 = new JSONObject();
dad1.put("he", jsonObject2);
JSONObject dad2 = new JSONObject();
dad2.put("he", jsonObject2.toString());
Log.i("TESTJSON", "dad1 :\n" + dad1.toString());
Log.i("TESTJSON", "dad2 :\n" + dad2.toString());
JSONObject dad = new JSONObject();
dad.put("he", dad2.toString());
Log.i("TESTJSON", "dad :\n" + dad.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
我们知道,JSON是一种递归的数据结构,可以通过put()方法将一个对象与KEY搭配放入JSON对象作为一个字段。与此对应,通过get()方法从JSON对象中得到该字段对应的值。put()/get()方法很友好,支持基本数据类型的自动装箱拆箱。
本文探讨JSON与String的关系,所以上述测试代码以JSONObject和String对象对比测试。
首先创建一个JSONObject:sonJsonObject,有两个String型的字段sonkey1、sonkey2。
创建两个JSONObject:jsonObject1、jsonObject2,将sonJsonObject分别以其自身、通过toString()转化得到的String对象放入son字段。
再创建两个上一级的JSONObject:dad1、dad2,将jsonObject2分别以其自身、通过toString()转化得到的String对象放入he字段。
最后创建一个最高一级JSONObject:dad,将dad2通过toString()转化得到的String对象放入he字段。
最后,将上述几个对象通过toString()转成String通过log打印出来。
ouput log:
09-07 17:39:22.294 6887 6887 I TESTJSON: json1 :
09-07 17:39:22.294 6887 6887 I TESTJSON: {"key1":"value1","son":{"sonkey1":"sonvalue1","sonkey2":"sonvalue2"}}
09-07 17:39:22.294 6887 6887 I TESTJSON: json2 :
09-07 17:39:22.294 6887 6887 I TESTJSON: {"key1":"value1","son":"{\"sonkey1\":\"sonvalue1\",\"sonkey2\":\"sonvalue2\"}"}
09-07 17:39:22.294 6887 6887 I TESTJSON: dad1 :
09-07 17:39:22.294 6887 6887 I TESTJSON: {"he":{"key1":"value1","son":"{\"sonkey1\":\"sonvalue1\",\"sonkey2\":\"sonvalue2\"}"}}
09-07 17:39:22.294 6887 6887 I TESTJSON: dad2 :
09-07 17:39:22.294 6887 6887 I TESTJSON: {"he":"{\"key1\":\"value1\",\"son\":\"{\\\"sonkey1\\\":\\\"sonvalue1\\\",\\\"sonkey2\\\":\\\"sonvalue2\\\"}\"}"}
09-07 17:39:22.294 6887 6887 I TESTJSON: dad :
09-07 17:39:22.294 6887 6887 I TESTJSON: {"he":"{\"he\":\"{\\\"key1\\\":\\\"value1\\\",\\\"son\\\":\\\"{\\\\\\\"sonkey1\\\\\\\":\\\\\\\"sonvalue1\\\\\\\",\\\\\\\"sonkey2\\\\\\\":\\\\\\\"sonvalue2\\\\\\\"}\\\"}\"}"}
从log上看到,通过JSONObject.toString()得到String放入的字段,会开始有转义字符出现,在层级最多的dad对象,转义符累积到7层。下面先从JSONObject.toString()的角度解释一下。
2.toString()
JSONObject.toString()源代码:
/**
* Encodes this object as a compact JSON string, such as:
* <pre>{"query":"Pizza","locations":[94043,90210]}</pre>
*/
@Override public String toString() {
try {
JSONStringer stringer = new JSONStringer();
writeTo(stringer);
return stringer.toString();
} catch (JSONException e) {
return null;
}
}
void writeTo(JSONStringer stringer) throws JSONException {
stringer.object();
for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
stringer.key(entry.getKey()).value(entry.getValue());
}
stringer.endObject();
}
使用了一个辅助类JSONStringer。开始和结束分别调用了object()/endObject(),中间是一个for循环。先看两头:
/**
* Begins encoding a new object. Each call to this method must be paired
* with a call to {@link #endObject}.
*
* @return this stringer.
*/
public JSONStringer object() throws JSONException {
return open(Scope.EMPTY_OBJECT, "{");
}
/**
* Ends encoding the current object.
*
* @return this stringer.
*/
public JSONStringer endObject() throws JSONException {
return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
}
继续看open()/close():
/**
* Enters a new scope by appending any necessary whitespace and the given
* bracket.
*/
JSONStringer open(Scope empty, String openBracket) throws JSONException {
if (stack.isEmpty() && out.length() > 0) {
throw new JSONException("Nesting problem: multiple top-level roots");
}
beforeValue();
stack.add(empty);
out.append(openBracket);
return this;
}
/**
* Closes the current scope by appending any necessary whitespace and the
* given bracket.
*/
JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
Scope context = peek();
if (context != nonempty && context != empty) {
throw new JSONException("Nesting problem");
}
stack.remove(stack.size() - 1);
if (context == nonempty) {
newline();
}
out.append(closeBracket);
return this;
}
/**
* Returns the value on the top of the stack.
*/
private Scope peek() throws JSONException {
if (stack.isEmpty()) {
throw new JSONException("Nesting problem");
}
return stack.get(stack.size() - 1);
}
出现两个缓存数据结构stack和out:
/**
* Unlike the original implementation, this stack isn't limited to 20
* levels of nesting.
*/
private final List<Scope> stack = new ArrayList<Scope>();
/**
* Lexical scoping elements within this stringer, necessary to insert the
* appropriate separator characters (ie. commas and colons) and to detect
* nesting errors.
*/
enum Scope { ... }
stack是一个ArrayList,起到栈的作用,JSONObject是一个递归的数据结构,用栈来辅助解析。peek()即从栈顶取元素。
/** The output data, containing at most one top-level array or object. */
final StringBuilder out = new StringBuilder();
out是一个StringBuilder,显然是用来辅助拼接输出的String。
搞清楚了数据结构,看object()+open()的逻辑:
(1)向stack栈顶压入Scope.EMPTY_OBJECT
(2)向out添加 {
endObject()+close()的逻辑:
(1)取出栈顶元素
(2)如果(1)取到的元素是Scope.NONEMPTY_OBJECT,通过方法newline()向out增加一行新的内容
(3)向out添加 }
可以以一个没有任何字段内容的JSONObject的toString()想象一下这个过程。
接下来看复杂的,JSONObject.writeTo(JSONStringer)中间的for循环部分:
void writeTo(JSONStringer stringer) throws JSONException {
stringer.object();
for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
stringer.key(entry.getKey()).value(entry.getValue());
}
stringer.endObject();
}
出现了两个重要的方法key()/value()。先看kay():
/**
* Encodes the key (property name) to this stringer.
*
* @param name the name of the forthcoming value. May not be null.
* @return this stringer.
*/
public JSONStringer key(String name) throws JSONException {
if (name == null) {
throw new JSONException("Names must be non-null");
}
beforeKey();
string(name);
return this;
}
beforeKey()作用是校验stack栈顶值是否正确,添加逗号,增加新行,维护栈顶值状态机:
/**
* Inserts any necessary separators and whitespace before a name. Also
* adjusts the stack to expect the key's value.
*/
private void beforeKey() throws JSONException {
Scope context = peek();
if (context == Scope.NONEMPTY_OBJECT) { // first in object
out.append(',');
} else if (context != Scope.EMPTY_OBJECT) { // not in an object!
throw new JSONException("Nesting problem");
}
newline();
replaceTop(Scope.DANGLING_KEY);
}
string()稍后一起看。接下来看看value(Object):
/**
* Encodes {@code value}.
*
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
* Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs}
* or {@link Double#isInfinite() infinities}.
* @return this stringer.
*/
public JSONStringer value(Object value) throws JSONException {
if (stack.isEmpty()) {
throw new JSONException("Nesting problem");
}
if (value instanceof JSONArray) {
((JSONArray) value).writeTo(this);
return this;
} else if (value instanceof JSONObject) {
((JSONObject) value).writeTo(this);
return this;
}
beforeValue();
if (value == null
|| value instanceof Boolean
|| value == JSONObject.NULL) {
out.append(value);
} else if (value instanceof Number) {
out.append(JSONObject.numberToString((Number) value));
} else {
string(value.toString());
}
return this;
}
(1)这里看到了递归调用writeTo():如果value是JSONObject或者JSONArray,那么调用value.writeTo(this)。
(2)如果value是Boolean或者Number,那么直接(或者简单转化后)添加到out。
(3)其他情况的Object,和key的处理一样,调用string()处理。
(4)在(1)和(2)之间调用了beforeValue(),校验stack栈顶值是否正确,添加逗号及冒号,增加新行,维护栈顶值状态机:
/**
* Inserts any necessary separators and whitespace before a literal value,
* inline array, or inline object. Also adjusts the stack to expect either a
* closing bracket or another element.
*/
private void beforeValue() throws JSONException {
if (stack.isEmpty()) {
return;
}
Scope context = peek();
if (context == Scope.EMPTY_ARRAY) { // first in array
replaceTop(Scope.NONEMPTY_ARRAY);
newline();
} else if (context == Scope.NONEMPTY_ARRAY) { // another in array
out.append(',');
newline();
} else if (context == Scope.DANGLING_KEY) { // value for key
out.append(indent == null ? ":" : ": ");
replaceTop(Scope.NONEMPTY_OBJECT);
} else if (context != Scope.NULL) {
throw new JSONException("Nesting problem");
}
}