Xjson - 是一款基于Kotlin编译器插件的Mini JSON库,旨在学习:
- 如何编写一个简单的Kotlin K2编译器插件(K2 compiler plugin)
- 了解Kotlin最新的K2编译器的前端(FIR)和后端(IR),以及FIR/IR分别能做的事
- 如何编写编译器插件的测试文件,配置gradle插件
- JSON库的内部原理
目前支持的特性:
- 内置原始类型(Int, String, Double, ...)
- 泛型
- 嵌套类
简单使用
当在数据类上标记@XSerializable
时,编译器会自动为其生成 XSerializer 类,以及对应的序列化和反序列化方法(serialize/deserialize)
序列化
kotlin
import com.icyrockton.xjson.runtime.annotation.XSerializable
import com.icyrockton.xjson.runtime.json.XJson
@XSerializable
data class Car(
val carName: String,
val carStock: Int,
val price: Double,
val isNewCar: Boolean,
)
fun main() {
val car = Car("Tesla Model Y", 200, 10.01, true)
println(XJson{}.encodeToString(car))
// {"carName": "Tesla Model Y","carStock": 200,"price": 10.01,"isNewCar": true}
}
反序列化
kotlin
import com.icyrockton.xjson.runtime.annotation.XSerializable
import com.icyrockton.xjson.runtime.json.XJson
@XSerializable
data class Car(
val carName: String,
val carStock: Int,
val price: Double,
val isNewCar: Boolean,
)
fun main() {
val str = """ {"carName": "Tesla Model Y","carStock": 200,"price": 10.01,"isNewCar": true} """.trimIndent()
println(XJson{}.decodeFromString<Car>(str))
// Car(carName=Tesla Model Y, carStock=200, price=10.01, isNewCar=true)
}
内部实现
项目目录说明
arduino
├─sample // 示例项目
├─xjson-plugin-gradle // gradle插件,用于将K2编译器插件应用于gradle项目中
├─xjson-plugin // XJson的编译器插件
└─xjson-runtime // XJson的运行时库
重要数据结构
- Descriptor 记录了一个数据类的结构。用于序列化/反序列化时,得到数据类中各个property的key名称。
- Encoder/Decoder 用于序列化/反序列化各种数据类型
- XSerializer 描述了一个数据类是如何进行序列化/反序列化的
- JsonLexer 解析JSON结构
- JsonWriter 写入JSON
手动编写一个XSerializer
XSerializer是编译器插件所要生成的核心类,它针对每个数据类中的所有property生成serialize/deserialize方法
为了明白编译器插件是如何生成XSerializer的,我们首先手写一个XSerializer
假设有一个数据类Car
kotlin
data class Car(
val carName: String,
val carStock: Int,
val price: Double,
val isNewCar: Boolean,
)
我们为其编写CarSerializer
kotlin
import com.icyrockton.xjson.runtime.XSerializer
import com.icyrockton.xjson.runtime.encoding.Decoder
import com.icyrockton.xjson.runtime.encoding.Encoder
import com.icyrockton.xjson.runtime.encoding.beginStructure
import com.icyrockton.xjson.runtime.core.serializer
import com.icyrockton.xjson.runtime.descriptor.Descriptor
import com.icyrockton.xjson.runtime.descriptor.Descriptor.Companion.UNKNOWN_ELEMENT
import com.icyrockton.xjson.runtime.descriptor.buildObjSerialDescriptor
class CarSerializer : XSerializer<Car> {
override val descriptor: Descriptor
get() = buildObjSerialDescriptor("car") {
element<String>("carName")
element<Int>("carStock")
element<Double>("price")
element<Boolean>("isNewCar")
}
override fun serialize(encoder: Encoder, value: Car) {
encoder.beginStructure(descriptor) {
encodeSerializableElement(descriptor, 0, String.serializer(), value.carName)
encodeSerializableElement(descriptor, 1, Int.serializer(), value.carStock)
encodeSerializableElement(descriptor, 2, Double.serializer(), value.price)
encodeSerializableElement(descriptor, 3, Boolean.serializer(), value.isNewCar)
}
}
override fun deserialize(decoder: Decoder): Car {
val composite = decoder.beginStructure(descriptor)
var carName: String? = null
var carStock: Int? = null
var price: Double? = null
var isNewCar: Boolean? = null
while (true) {
val idx = composite.decodeElementIndex(descriptor)
when (idx) {
0 -> carName = composite.decodeSerializableElement(descriptor, 0, String.serializer())
1 -> carStock = composite.decodeSerializableElement(descriptor, 1, Int.serializer())
2 -> price = composite.decodeSerializableElement(descriptor, 2, Double.serializer())
3 -> isNewCar = composite.decodeSerializableElement(descriptor, 2, Boolean.serializer())
UNKNOWN_ELEMENT -> break
}
}
composite.endStructure(descriptor)
require(carName != null)
require(carStock != null)
require(price != null)
require(isNewCar != null)
return Car(carName, carStock, price, isNewCar)
}
}
具体来说,CarSerializer
实现了
- 描述
Car
数据类的descriptor - 在
serialize
方法中,针对Car
中的所有属性,调用encodeXXXElement
方法,对每个元素进行序列化 - 在
deserialize
方法中,先利用decoder的decodeElementIndex
方法,不断解码当前property的索引 (在JSON中,每个字段出现的顺序可能与Car声明的property顺序不一致),确定需要反序列化的下一个元素,调用decodeXXXElement
,赋值到局部变量中。最后调用Car
的构造函数,实例化新的Car
返回。
所以XJson编译器插件的目标就是生成以上的XSerializer
示例代码如下
kotlin
@XSerializable
data class Car(
val carName: String,
val carStock: Int,
val price: Double,
val isNewCar: Boolean,
) {
// XJson编译器插件自动生成
class $serializer$: XSerializer{
override val descriptor: Descriptor
override fun serialize(encoder: Encoder, value: Car) {
...
}
override fun deserialize(decoder: Decoder): Car{
...
}
}
}
探究Kotlin Compiler的结构
Kotlin编译器现阶段(K2),分为了前端和后端
前端: Kotlin的前端(F ront I ntermediate R epresentation)也被简称为FIR。 FIR处理与平台无关的Kotlin代码,主要完成了两件事:首先进行解析(resolve),然后进行检查(checke)
- 解析:import解析,supertype解析,基于约束系统的类型推导,符号查找(e.g. 一个函数的查找) 等
- 检查:利用不同的Checker对解析后代码进行检查,报告语法/语义/类型等错误(e.g. reified只能用在inline函数中)
后端: Kotlin的后端(I ntermediate Representation)被简称为IR。该阶段主要完成两件事:代码低级化(lowering)和代码生成(codegen)
- 代码低级化(lowering):该阶段会将编译到的不同平台(JVM, JS, Wasm)的特性,将Kotlin语言中高级的特征,经过一个一个的lowering phase,低级化(lower)代码。举一个例子,例如Kotlin的suspend协程函数,AddContinuationLowering 阶段,会为suspend函数填充一个Continuation参数,用于满足CPS特性(C ontinuation P assing Style)
- 最终代码生成(codegen):Kotlin使用了ASM库,将低级化后的IR进行目标平台的代码生成
目前K2编译器插件支持对FIR和IR进行扩展
- FIR:支持简单的代码生成,对访问修饰符进行修改(比如All-open插件),自定义Checker
- IR:复杂代码生成
实现XJson编译器插件
XJson同时使用了FIR和IR的扩展点
- FIR:搜索被
@XSerializableX
标注的数据类,在其内部对$serializer$
类的定义进行生成,但不生成方法的函数体。 - IR:生成
$serializer$
的所有方法
所有代码实现均放在xjson-plugin目录下