在仓颉中,我们通过serialization 模块 和 encoding.json 模块实现了JSON数据的序列化与返序列化,对@JSON宏的实现思路实际是将重复性的代码通过宏的特性进行一层封装,然后再编译期对代码进行展开,接下来我们将通过代码实操来讲解下如何实现一个简易的@JSON宏

首先我们看先不使用宏情况下实现的代码

class ToDoItem <: Serializable < ToDoItem > {
    public var title: String = "这是标题"
    public var isChecked: Bool = false

    public func serialize(): DataModel {
        return DataModelStruct().add(field < String >("title", title)).add(field < Bool >("isChecked", isChecked)) 
    }

    public static func deserialize(dm: DataModel): ToDoItem {
        let dms = match(dm) {
            case data: DataModelStruct => data
            case _ => throw Exception("this data is not DataModelStruct")
        }
        let result = ToDoItem()

        result.title = String.deserialize(dms.get("title"));

        result.isChecked = Bool.deserialize(dms.get("isChecked"));
        return result
    }

    public func toJSON(): JsonValue {
        return serialize().toJson()
    }
}

在以上代码中,我们需要把接口继承部分以及serialize和deserialize的这部分公共代码提取到宏里面进行动态拼接,同时我们需要动态获取变量和变量的对应类型,接下来我们将一步步实现@JSON宏,我们先实现一个基础宏,先用来获取Class名称,以便于我们实现接口继承的代码模板,具体实现代码如下

macro package cjmarcos.macros
import std.ast.*
import encoding.json.*
import serialization.serialization.*
import std.collection.*


public macro JSON(input: Tokens): Tokens {
    var decl = ClassDecl(input)
    var newClass = quote(class $(decl.identifier) <: Serializable<$(decl.identifier)> {)
    newClass += quote(})
    return newClass
}

以上代码中我们使用了ClassDecl进行对Tokens的解析,然后通过identifier获取class的标识符,即我们上述所属的类名ToDoItem,接下来,我们可以通过获取decl的具体的作用域内的代码内容来获取我们所需要转换的变量名以及变量类型并实现序列化与返序列化的字段映射模板拼接,具体实现代码如下:

var serializeToken = quote(public func serialize(): DataModel {
        return DataModelStruct())
var deserializeToken = quote(
        public static func deserialize(dm: DataModel): $(decl.identifier) {
            let dms = match (dm) {
                case data: DataModelStruct => data
                case _ => throw Exception("this data is not DataModelStruct")
            }
            let result = $(decl.identifier)()
)

for(item in decl.body.decls) {
    if (item is VarDecl) {
        var itemType = (item as VarDecl).getOrThrow().declType.toTokens()
        serializeToken += quote(.add(field<$itemType>($(item.identifier.value), $(item.identifier))))
        deserializeToken += quote(
            result.$(item.identifier) = $(itemType).deserialize(dms.get($(item.identifier.value)));
        )
    }
}


serializeToken += quote(})
deserializeToken += quote(return result})

在以上示例代码中,我们使用了item is VarDecl来判断内容成员是否为var声明的变量类型,然后通过获取declType(即变量对应的类型)并转为Tokens用于对模板的拼接,通过以上

的代码,我们就可以完整的获取到我们所需要的变量名称以及变量对应的类型,并完成了对serialize和deserialize重写的模板代码,接下来我们对所有的代码模板进行拼接使其可以形成完整闭环的模板代码,具体如下

public macro JSON(input: Tokens): Tokens {
    var decl = ClassDecl(input)
    var newClass = quote(class $(decl.identifier) <: Serializable<$(decl.identifier)> {)
    newClass += quote($(decl.body.decls))
    var serializeToken = quote(public func serialize(): DataModel {
        return DataModelStruct())
    var deserializeToken = quote(
        public static func deserialize(dm: DataModel): $(decl.identifier) {
            let dms = match (dm) {
                case data: DataModelStruct => data
                case _ => throw Exception("this data is not DataModelStruct")
            }
            let result = $(decl.identifier)()
    )

    for(item in decl.body.decls) {
        if (item is VarDecl) {
            var itemType = (item as VarDecl).getOrThrow().declType.toTokens()
            serializeToken += quote(.add(field<$itemType>($(item.identifier.value), $(item.identifier))))
            deserializeToken += quote(
                result.$(item.identifier) = $(itemType).deserialize(dms.get($(item.identifier.value)));
            )
        }
    }
    serializeToken += quote(})
    deserializeToken += quote(return result})
    newClass += serializeToken
    newClass += deserializeToken
    newClass += quote(
        public func toJSON(): JsonValue {
            return serialize().toJson()
        }
    )
    newClass += quote(})
    return newClass
}

最终,调用宏后展开的代码将于我们开始时的示例代码保持一致,宏的具体调用代码如下:

import cjmarcos.macros.*


@JSON
class ToDoItem {
    public var title: String = '这是标题'
    public var isChecked: Bool = false
}


let todo = ToDoItem()
println(todo.toJSON().toString()) // {"title":"这是标题","isChecked":false}

此时我们就完整的实现了使用宏实现对任意类的序列化与返序列化的实现

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐