JsonElement
先来看下JsonElement继承关系:
JsonElement (com.google.gson)
JsonObject (com.google.gson)
JsonArray (com.google.gson)
JsonNull (com.google.gson)
JsonPrimitive (com.google.gson)
json中的类型也就是上面四种。JsonPrimitive是基础类型,例如,String,Number,Boolean。
JsonNull用来构筑完备集合。从字面上理解,JsonElement便是把json解析之后的元素。比较有意思的JsonArray与JsonObject的数据结构。我们重点来分析下,这两相类。
Gson与JsonElement相关的数据结构
先来看下json的文法:
object = {} | { members }
members = pair | pair , members
pair = string : value
array = [] | [ elements ]
elements = value | value , elements
value = string | number | object | array | true | false | null
string = "" | " chars "
chars = char | char chars
char = any-Unicode-character-except-"-or-\-or- control-character | \" | \\ | \/ | \b | \f | \n | \r | \t | \u four-hex-digits
number = int | int frac | int exp | int frac exp
int = digit | digit1-9 digits | - digit | - digit1-9 digits
frac = . digits
exp = e digits
digits = digit | digit digits
e = e | e+ | e- | E | E+ | E-
首先,无论是object还是array,都应当是多叉树的结构。
object可以是{}或者{members},members是键值对或者members,也就是0个,1个或多个pair组成,而pair中value又可定义为object,所以是个树状结构。而array是由[]或者[elements]推导出,也就是任意个value组成。而value中可以含有object,object可以有嵌套。
所以,在JsonObject和JsonArray中均有树状结构,其本身可以视为一个note:
public final class JsonObject extends JsonElement {
...
private final LinkedTreeMap<String, JsonElement> members =
new LinkedTreeMap<String, JsonElement>();
...
}
public final class JsonArray extends JsonElement implements Iterable<JsonElement> {
...
private final List<JsonElement> elements;
...
}
JsonObject会有键值对,为了保证顺序,以及速度,所以使用了自定义的一个数据结构。
JsonElement的生成
生成过程:
package com.google.gson.internal;
Streams.java
public static JsonElement parse(JsonReader reader) throws JsonParseException {
boolean isEmpty = true;
try {
reader.peek();
isEmpty = false;
return TypeAdapters.JSON_ELEMENT.read(reader);
} catch (EOFException e) {
/*
* For compatibility with JSON 1.5 and earlier, we return a JsonNull for
* empty documents instead of throwing.
*/
if (isEmpty) {
return JsonNull.INSTANCE;
}
// The stream ended prematurely so it is likely a syntax error.
throw new JsonSyntaxException(e);
} catch (MalformedJsonException e) {
throw new JsonSyntaxException(e);
} catch (IOException e) {
throw new JsonIOException(e);
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
前面的文章已经简单分析了JsonReader,其peek方法便是生成一个Token。而生成JsonElement的方法在TypeAdapter中。
public static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() {
@Override public JsonElement read(JsonReader in) throws IOException {
switch (in.peek()) {
case STRING:
return new JsonPrimitive(in.nextString());
case NUMBER:
String number = in.nextString();
return new JsonPrimitive(new LazilyParsedNumber(number));
case BOOLEAN:
return new JsonPrimitive(in.nextBoolean());
case NULL:
in.nextNull();
return JsonNull.INSTANCE;
case BEGIN_ARRAY:
JsonArray array = new JsonArray();
in.beginArray();
while (in.hasNext()) {
array.add(read(in));
}
in.endArray();
return array;
case BEGIN_OBJECT:
JsonObject object = new JsonObject();
in.beginObject();
while (in.hasNext()) {
object.add(in.nextName(), read(in));
}
in.endObject();
return object;
case END_DOCUMENT:
case NAME:
case END_OBJECT:
case END_ARRAY:
default:
throw new IllegalArgumentException();
}
}
@Override public void write(JsonWriter out, JsonElement value) throws IOException {
if (value == null || value.isJsonNull()) {
out.nullValue();
} else if (value.isJsonPrimitive()) {
JsonPrimitive primitive = value.getAsJsonPrimitive();
if (primitive.isNumber()) {
out.value(primitive.getAsNumber());
} else if (primitive.isBoolean()) {
out.value(primitive.getAsBoolean());
} else {
out.value(primitive.getAsString());
}
} else if (value.isJsonArray()) {
out.beginArray();
for (JsonElement e : value.getAsJsonArray()) {
write(out, e);
}
out.endArray();
} else if (value.isJsonObject()) {
out.beginObject();
for (Map.Entry<String, JsonElement> e : value.getAsJsonObject().entrySet()) {
out.name(e.getKey());
write(out, e.getValue());
}
out.endObject();
} else {
throw new IllegalArgumentException("Couldn't write " + value.getClass());
}
}
};
以object为例,如果是BEGIN_OBJECT,则创建JsonObject实例,设置读状态,循环读,读完关闭并返回实例。来看个两个比较有意思的方法,JsonReader的beginObject和endObject
public void beginObject() throws IOException {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
if (p == PEEKED_BEGIN_OBJECT) {
push(JsonScope.EMPTY_OBJECT);
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected BEGIN_OBJECT but was " + peek() + locationString());
}
}
开始读object的时候,会将JsonScope.EMPTY_OBJECT压栈。
public void endObject() throws IOException {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
if (p == PEEKED_END_OBJECT) {
stackSize--;
pathNames[stackSize] = null; // Free the last path name so that it can be garbage collected!
pathIndices[stackSize - 1]++;
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected END_OBJECT but was " + peek() + locationString());
}
}
读完之后,将开始读object压入的EMPTY_OBJECT清空。这是什么操作?
在doPeek函数中会栈顶元素纪录着当前所处理的字符类型。
if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT)
{
stack[stackSize - 1] = JsonScope.DANGLING_NAME;
// Look for a comma before the next element.
if (peekStack == JsonScope.NONEMPTY_OBJECT) {
int c = nextNonWhitespace(true);
switch (c) {
case '}':
return peeked = PEEKED_END_OBJECT;
case ';':
checkLenient(); // fall-through
case ',':
break;
default:
throw syntaxError("Unterminated object");
}
}
int c = nextNonWhitespace(true);
switch (c) {
case '"':
return peeked = PEEKED_DOUBLE_QUOTED_NAME;
case '\'':
checkLenient();
return peeked = PEEKED_SINGLE_QUOTED_NAME;
case '}':
if (peekStack != JsonScope.NONEMPTY_OBJECT) {
return peeked = PEEKED_END_OBJECT;
} else {
throw syntaxError("Expected name");
}
default:
checkLenient();
pos--; // Don't consume the first character in an unquoted string.
if (isLiteral((char) c)) {
return peeked = PEEKED_UNQUOTED_NAME;
} else {
throw syntaxError("Expected name");
}
}
如果是EMPTY_OBJECT或者NONEMPTY_OBJECT都会走到这个流程,
如果是EMPTY_OBJECT的话,输入的有效字符是“ “ ”,以及” }“ ,如果是NONEMPTY_OBJECT的话,有效字符是” ,“ 以及”}“。从这里看出,EMPTY_OBJECT是还没有解析内容的状态。如果接下来的输入是”}“,则才是真正的EMPTY,不要被名字引起误会。
所以刚开始解析的时候,会把EMPTY_OBJECT这种状态压入栈中。