XJson - 基于Kotlin编译器插件(K2)的Mini Json库

Xjson - 是一款基于Kotlin编译器插件的Mini JSON库,旨在学习:

  1. 如何编写一个简单的Kotlin K2编译器插件(K2 compiler plugin)
  2. 了解Kotlin最新的K2编译器的前端(FIR)和后端(IR),以及FIR/IR分别能做的事
  3. 如何编写编译器插件的测试文件,配置gradle插件
  4. JSON库的内部原理

目前支持的特性:

  • 内置原始类型(Int, String, Double, ...)
  • 泛型
  • 嵌套类

Icyrockton/xjson 点我跳转到GitHub

简单使用

当在数据类上标记@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实现了

  1. 描述Car数据类的descriptor
  2. serialize方法中,针对Car中的所有属性,调用encodeXXXElement方法,对每个元素进行序列化
  3. 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目录下

相关推荐
hlsd#10 分钟前
go mod 依赖管理
开发语言·后端·golang
陈大爷(有低保)14 分钟前
三层架构和MVC以及它们的融合
后端·mvc
亦世凡华、15 分钟前
【启程Golang之旅】从零开始构建可扩展的微服务架构
开发语言·经验分享·后端·golang
河西石头15 分钟前
一步一步从asp.net core mvc中访问asp.net core WebApi
后端·asp.net·mvc·.net core访问api·httpclient的使用
2401_8574396927 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66628 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
阿华的代码王国1 小时前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
小码编匠1 小时前
领域驱动设计(DDD)要点及C#示例
后端·c#·领域驱动设计
德育处主任Pro2 小时前
『Django』APIView基于类的用法
后端·python·django
哎呦没4 小时前
SpringBoot框架下的资产管理自动化
java·spring boot·后端