JSON 与 Gson 兼容层
Gson 形状的树状 API,以及运行期读取类字段的对象绑定。
Teyru 的 JSON 分成两层:lib/10_json.teyru 是 Gson 的树状 API(纯 Teyru,不需要
编译器配合),lib/19_gson.teyru 加上编译器的对象绑定。
树状 API(lib/10)
JsonElement、JsonObject、JsonArray、JsonPrimitive、JsonNull、
JsonParser.parseString、Gson、GsonBuilder、JsonSyntaxException,
行为对齐 Gson 2.10,包含几个容易做错的细节:
JsonNull继承JsonElement而不是JsonPrimitive。- 解析后的数字保留原始字面量,所以
1e5打印回来还是1e5,不会变成100000.0。 - 转义规则照 Gson 的替换表:
"与\会转义、五个短形式\b \t \n \f \r、 其余小于 0x20 的用\u00xx(小写十六进制)、/不转义、非 ASCII 以 UTF-8 输出而不是\u转义,只有 U+2028/U+2029 例外;<、>、&、=、'只在 htmlSafe 的写法(默认的new Gson())下转义,JsonElement.toString()不转义。 - 空对象与空数组在 pretty print 时保持一行(
{}、[])。
与 Gson 的差异(刻意的):
JsonParser.parseString是严格的(RFC 8259)。Gson 宽松模式接受的{a:1}、'单引号'、01、+1、.5、1.、NaN、[1,]一律拒绝并抛出JsonSyntaxException;空文档也拒绝,尾端多余数据一律拒绝。entrySet()返回List<JsonMember>、keySet()返回List<String>(Gson 给的是Set);JsonMember的getKey/getValue对应Map.Entry。- 数字只到
long,没有getAsBigDecimal/getAsBigInteger。 - 数值访问器抛出
IllegalArgumentException而不是NumberFormatException(前者是后者的父类,所以 catch 父类的代码不受影响;指名NumberFormatException的 catch 拦不到它们)。
对象绑定(lib/19 加上编译器)
Gson 用反射把对象绑定到 JSON。这里编译器代劳:
import teyru.*
class Address {
String city
String zip
}
class Person {
String name
int age
@SerializedName("home_address") Address address
}
Gson gson = new Gson()
Person p = gson.fromJson(json, Person.class) // 不需要 cast
String out = gson.toJson(p)编译器看到 fromJson(s, Person.class) 就在 Person 上生成
__teyruJsonRead(JsonElement) 与 __teyruJsonWrite(),并把调用改写成它们。因此:
- 不需要 cast:Gson 的
<T> T fromJson(String, Class<T>)在 Teyru 无法表达 (Class不是泛型),但改写后的返回类型就是目标类本身。 - 字段类型没有映射是编译错误(
TY-TYP-0110;List/Map这类 prelude 类是TY-TYP-0108),而不是运行时从反射适配器深处抛出的IllegalArgumentException。 - 绑定是按调用点生成的:没有用到就不生成,而且字段列表是生成当时的(Lombok 生成的成员也在内)。
支持的字段类型:String、boolean/byte/short/char/int/long/float/
double 与其装箱类型、enum(写成常量名,读回来比对名字,与 Gson 相同;名字对不上
时 Gson 留 null,这里抛 JsonParseException)、其他可绑定的类(递归)、
Object(保留原始树:读进来的 JsonObject 原样写回去),以及容器——List/Set/
Map/Deque 等接口与其实、数组,容器里的对象元素也绑定(元素类型由编译器写进
字段描述符,Field.getElementType() 读得到;反射本身仍然没有泛型类型)。char 和 Gson
一样写成单字符字符串、也从字符串读回来。
GsonBuilder 的旋钮(serializeNulls/setPrettyPrinting/setLenient/
setFieldNamingPolicy 的五种策略/excludeFieldsWithModifiers/
excludeFieldsWithoutExposeAnnotation/setVersion)、@Expose/@Since/@Until/
@SerializedName 的过滤与改名、JsonSerializer/JsonDeserializer 适配器
(registerTypeAdapter/registerTypeHierarchyAdapter/@JsonAdapter)与流式
JsonReader/JsonWriter 都在(t166_gson_builder、t167_gson_annotations、
t168_gson_adapters;容器是 t157_json_collections、t165_json_container_elements)。
仍然不支持的:元素类型只记一层(List<List<Person>> 的内层元素按文档的形状判读,
里面是对象或数组就明确拒绝;声明为 Object 的元素也一样)、数组的 component 是
? super 或未定界类型变量时没有类可以建数组(该成员明确拒绝)、
@SerializedName 的 alternate(多个名字要用重复标注写,第一个是写出用的名字,
其余是读入也接受的替代名)、@JsonAdapter 用字符串命名适配器类。
运行时回退表
静态类型是 Object 的引用(gson.toJson(someObject))编译器看不穿,这时走
JsonBinding 这张表:每个生成过绑定的类在自己的静态初始化里注册reader/
writer,查表用对象自己的类——这正是 Gson 反射给出的答案。
Object 可以装着程序里的任何类,所以这种调用点一出现,编译器就替程序中声明的
每个类都生成一份绑定(接口、抽象类、没有无参构造函数又无法从 JSON 读取的
类除外;那些生成不出来的就不登记,运行时问到会说没有绑定)。没有这个调用点的
程序仍然只为真正用到的类生成绑定。
相关测试
tests/programs/t93_json.teyru— 树状 API:转义、数字、pretty print、往返。tests/programs/t101_gson.teyru— 对象绑定:嵌套类、@SerializedName、 缺字段、运行时回退表。tests/programs/t140_json_binding_edges.teyru— 边界:char往返、Object字段里的树、只通过Object传递的类、enum 与未知的常量名称。