diff --git a/src/json/JsonReader.cj b/src/json/JsonReader.cj index 378d83e..29ccbaf 100644 --- a/src/json/JsonReader.cj +++ b/src/json/JsonReader.cj @@ -133,9 +133,38 @@ public class JsonReader { case None => () } } + // 父类字段(@JsonParent 宏生成的静态导入方法,沿继承链逐层回填) + readParentFields(value, ct, instance, options) instance } + /// 沿 superClass 链调用每层的 importJsonFields 静态方法回填父类字段。 + /// 无宏的父类(hasImportFields 缓存判断)静默跳过;宏生成的方法内部按 + /// if (k == "字段名") 忽略未知键,因此直接把全部 JSON 键传入即可。 + private static func readParentFields(value: JsonValue, ct: ClassTypeInfo, instance: Any, options: JsonOption): Unit { + let jobj = value.asObject() + var parent = ct.superClass + while (let Some(p) <- parent) { + if (ReflectionCache.hasImportFields(p)) { + try { + let fn = p.getStaticFunction("importJsonFields", + [p, TypeInfo.of>()]) + var pjson = HashMap() + for ((k, jv) in jobj.getFields()) { + if (!(jv is JsonNull)) { + pjson[k] = jsonToAny(jv) + } + } + let args: Array = [instance, pjson] + fn.apply(p, args) + } catch (_: Exception) { + // 导入失败(如类型不匹配)不中断整体反序列化 + } + } + parent = p.superClass + } + } + /// Array:经 ArrayList 构造 + add + toArray private static func readArray(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any { let listTypeName = "std.collection.ArrayList<${elemName}>" diff --git a/src/json/JsonSerializer_test.cj b/src/json/JsonSerializer_test.cj index a03041b..f473741 100644 --- a/src/json/JsonSerializer_test.cj +++ b/src/json/JsonSerializer_test.cj @@ -13,6 +13,7 @@ package simapi_serialization.json import std.collection.* import std.unittest.* import std.unittest.testmacro.* +import simapi_serialization.macros.* enum Role { | Admin @@ -26,10 +27,16 @@ class Address { public var zip: String = "" // 无下划线 → 原样输出 zip } -// 继承测试:父类字段(_id/_createdAt)受仓颉反射「声明类严格校验」限制 +// 继承测试:父类标注 @JsonParent 宏 → 自动补 open + 生成导出/导入静态方法, +// 父类字段(_id/_createdAt)经宏的静态类型访问绕开反射声明类校验 +@JsonParent open class BaseUser { public var _id: String = "" public var _createdAt: String = "" + @JsonIgnore + public var _temp: String = "" + @JsonPropertyName["parent_alias"] + public var _parentAlias: String = "" } class User <: BaseUser { @@ -48,8 +55,27 @@ class User <: BaseUser { public var temp: String = "secret" // 无下划线 + 忽略 } +// 多层继承:爷(标宏)→ 父(不标宏)→ 子 +@JsonParent +open class GrandParent { + public var _gp: String = "" +} + +open class MidParent <: GrandParent { + public var _mp: String = "" +} + +class LeafChild <: MidParent { + public var _lc: String = "" + public init() {} +} + func makeUser(): User { let u = User() + u._id = "u-1" + u._createdAt = "2025-01-01" + u._temp = "secret-parent" + u._parentAlias = "parent-xiaoming" u._name = "alice" u._age = 30 u.score = 88.5 @@ -117,9 +143,14 @@ public class JsonSerializerTests { // 嵌套对象 @Expect(json.contains("\"_city\": \"beijing\""), true) @Expect(json.contains("\"zip\": \"100000\""), true) - // 继承限制:父类字段不序列化 - @Expect(json.contains("_id"), false) - @Expect(json.contains("_createdAt"), false) + // 父类字段(@JsonParent 宏):_id/_createdAt 输出,@JsonIgnore 父类字段不输出 + @Expect(json.contains("\"_id\": \"u-1\""), true) + @Expect(json.contains("\"_createdAt\": \"2025-01-01\""), true) + // 父类 @JsonPropertyName["parent_alias"] 生效 + @Expect(json.contains("\"parent_alias\": \"parent-xiaoming\""), true) + @Expect(json.contains("_parentAlias"), false) + // 父类 @JsonIgnore _temp 不输出 + @Expect(json.contains("secret-parent"), false) } /// 对象反序列化往返 @@ -135,6 +166,10 @@ public class JsonSerializerTests { @Expect(u2.addr.zip, "100000") @Expect(u2._alias, "xiaoming") @Expect(u2.temp, "secret") // @JsonIgnore:反序列化不改动默认值 + // 父类字段往返(@JsonParent 宏) + @Expect(u2._id, "u-1") + @Expect(u2._createdAt, "2025-01-01") + @Expect(u2._parentAlias, "parent-xiaoming") // Option 往返 let optVal: ?String = u2.opt @Expect(optVal.isSome(), true) @@ -286,4 +321,57 @@ public class JsonSerializerTests { } @Expect(threw, true) } + + /// 父类字段序列化/反序列化(@JsonParent 宏):已并入 objectSerialize/objectRoundTrip, + /// 此处验证宏生成的静态方法可直接调用 + @TestCase + public func parentMacroMethods() { + let u = User() + u._id = "m-1" + u._createdAt = "2025-02-02" + u._parentAlias = "alias-m" + // 宏生成的导出方法:子类实例作父类参数 + let m = BaseUser.exportJsonFields(u) + @Expect(toStr(m["_id"]), "m-1") + @Expect(toStr(m["_createdAt"]), "2025-02-02") + @Expect(toStr(m["parent_alias"]), "alias-m") + // @JsonIgnore 父类字段不导出 + @Expect(m.contains("_temp"), false) + // 宏生成的导入方法 + var json = HashMap() + json["_id"] = "m-2" + json["parent_alias"] = "alias-m2" + BaseUser.importJsonFields(u, json) + @Expect(u._id, "m-2") + @Expect(u._parentAlias, "alias-m2") + @Expect(u._createdAt, "2025-02-02") // 未传的键保持原值 + } + + /// 多层继承:爷标宏、中间层不标宏 → 爷字段导出,中间层字段不导出 + @TestCase + public func multiLevelInheritance() { + let leaf = LeafChild() + leaf._gp = "gp-v" + leaf._mp = "mp-v" + leaf._lc = "lc-v" + + let json = JsonSerializer.Serialize(leaf) + @Expect(json.contains("\"_gp\": \"gp-v\""), true) // 爷字段(宏)导出 + @Expect(json.contains("_mp"), false) // 中间层无宏不导出 + @Expect(json.contains("\"_lc\": \"lc-v\""), true) // 子字段(反射)导出 + + let back = JsonSerializer.Deserialize(json) + @Expect(back._gp, "gp-v") + @Expect(back._lc, "lc-v") + @Expect(back._mp, "") // 中间层字段保持默认值 + } + + /// 宏自动补 open:GrandParent 未显式写 open(@JsonParent 自动补),继承链可正常编译 + @TestCase + public func parentAutoOpen() { + let leaf = LeafChild() + leaf._gp = "auto" + let json = JsonSerializer.Serialize(leaf) + @Expect(json.contains("\"_gp\": \"auto\""), true) + } } diff --git a/src/json/JsonWriter.cj b/src/json/JsonWriter.cj index f44b925..cd541e1 100644 --- a/src/json/JsonWriter.cj +++ b/src/json/JsonWriter.cj @@ -103,6 +103,7 @@ public class JsonWriter { private static func writeObject(value: Any, ct: ClassTypeInfo, options: JsonOption, depth: Int64): JsonValue { let obj = JsonObject() + // 1) 子类自身字段(反射) let fields = ReflectionCache.getFields(ct, options) for (f in fields) { // 只要不是 @JsonIgnore 的字段一律输出;None 字段输出 null @@ -112,9 +113,35 @@ public class JsonWriter { let raw = f.variable.getOrThrow().getValue(value) obj.put(f.jsonName, writeValue(raw, options, depth + 1)) } + // 2) 父类字段(@JsonParent 宏生成的静态导出方法,沿继承链逐层调用) + writeParentFields(value, ct, obj, options, depth) obj } + /// 沿 superClass 链调用每层的 exportJsonFields 静态方法并合并进 JSON。 + /// 无宏的父类(hasExportFields 缓存判断)静默跳过,继续向祖父层查找。 + private static func writeParentFields(value: Any, ct: ClassTypeInfo, obj: JsonObject, options: JsonOption, + depth: Int64): Unit { + var parent = ct.superClass + while (let Some(p) <- parent) { + if (ReflectionCache.hasExportFields(p)) { + try { + let f = p.getStaticFunction("exportJsonFields", [p]) + let args: Array = [value] // 子类实例作父类类型参数 + let m = f.apply(p, args) + if (let h: HashMap <- m) { + for ((k, v) in h) { + obj.put(k, writeValue(v, options, depth + 1)) + } + } + } catch (_: Exception) { + // 调用失败(不应发生)→ 跳过该层 + } + } + parent = p.superClass + } + } + /// 反射迭代集合:优先 toArray 转数组再 get(index);无 toArray(如 Array)直接用原集合 private static func forEachElement(collection: Any, action: (Any) -> Unit): Unit { let ti = TypeInfo.of(collection) diff --git a/src/json/ReflectionCache.cj b/src/json/ReflectionCache.cj index 3fd8ca3..5bf9488 100644 --- a/src/json/ReflectionCache.cj +++ b/src/json/ReflectionCache.cj @@ -40,6 +40,44 @@ public class ReflectionCache { private static let _cache = HashMap>() + /// 类型 → 是否有 @JsonParent 宏生成的 exportJsonFields(Bool) + private static let _exportCache = HashMap() + /// 类型 → 是否有 @JsonParent 宏生成的 importJsonFields(Bool) + private static let _importCache = HashMap() + + /** + * 该类型是否含 @JsonParent 宏生成的导出静态方法(含缓存)。 + */ + public static func hasExportFields(typeInfo: ClassTypeInfo): Bool { + hasParentMethod(typeInfo, "exportJsonFields", _exportCache) + } + + /** + * 该类型是否含 @JsonParent 宏生成的导入静态方法(含缓存)。 + */ + public static func hasImportFields(typeInfo: ClassTypeInfo): Bool { + hasParentMethod(typeInfo, "importJsonFields", _importCache) + } + + private static func hasParentMethod(typeInfo: ClassTypeInfo, name: String, cache: HashMap): Bool { + let key = typeInfo.qualifiedName + if (let Some(cached) <- cache.get(key)) { + return cached + } + let found = try { + if (name == "exportJsonFields") { + typeInfo.getStaticFunction(name, [typeInfo]) + } else { + typeInfo.getStaticFunction(name, [typeInfo, TypeInfo.of>()]) + } + true + } catch (_: Exception) { + false + } + cache[key] = found + found + } + /** * 获取类型的字段元数据(含缓存)。 * @param typeInfo 目标类类型。 diff --git a/src/macros/JsonParentMacro.cj b/src/macros/JsonParentMacro.cj new file mode 100644 index 0000000..a4a3406 --- /dev/null +++ b/src/macros/JsonParentMacro.cj @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2025 SimcuTeam. All rights reserved. + * @JsonParent:标注在父类上,自动生成父类字段的 JSON 导出/导入静态方法。 + * + * 背景:Cangjie 反射对实例成员的读写有「声明类严格校验」 + * (declaringClass != TypeInfo.of(instance)),父类字段无法用子类实例 + * 读写(IllegalTypeException)。本宏在编译期生成静态方法,方法体内用 + * 静态类型访问字段(非反射),从而绕开该限制;序列化库沿 superClass 链 + * 调用约定静态方法(见 JsonWriter/JsonReader)。 + * + * 用法: + * @JsonParent + * class BaseUser { + * public var _id: String = "" + * @JsonPropertyName["userId"] + * public var _userId: String = "" + * @JsonIgnore + * public var _temp: String = "" + * } + * + * 行为: + * - 非 open class 自动补 open + * - 生成 exportJsonFields(instance: T): HashMap + * - 生成 importJsonFields(instance: T, json: HashMap): Unit + * - @JsonIgnore 字段跳过;@JsonPropertyName 用注解名做 JSON 键 + */ + +macro package simapi_serialization.macros + +import std.ast.* +import std.collection.* + +/// 单个字段的元数据 +private class FieldInfo { + public var fieldName: String = "" // 源码字段名(如 _id) + public var jsonName: String = "" // JSON 键名(注解优先,否则字段名) + public var typeName: String = "" // 字段类型名(如 String) + public var isVar: Bool = true // var 可变(可导入);let 只导出 + + public init() {} + + public init(fieldName: String, jsonName: String, typeName: String, isVar: Bool) { + this.fieldName = fieldName + this.jsonName = jsonName + this.typeName = typeName + this.isVar = isVar + } +} + +/** + * 生成父类字段 JSON 导出/导入静态方法。 + * @param input 被标注的类声明。 + * @return 注入静态方法后的类声明。 + */ +public macro JsonParent(input: Tokens): Tokens { + let decl = parseDecl(input) + if (let cd: ClassDecl <- decl) { + // 1. open 处理:自动补 open(类被标注即表达「要被继承」的意图) + var hasOpen = false + for (m in cd.modifiers) { + if (m.keyword.kind == TokenKind.OPEN) { + hasOpen = true + } + } + if (!hasOpen) { + cd.modifiers.add(Modifier(Token(TokenKind.OPEN))) + } + + // 2. 收集字段 + let fields = collectFields(cd) + + // 3. 生成导出/导入静态方法(字符串拼接 + cangjieLex 解析) + let exportSrc = buildExportSource(cd.identifier.value, fields) + let importSrc = buildImportSource(cd.identifier.value, fields) + + if (let fd: FuncDecl <- parseDecl(cangjieLex(exportSrc))) { + cd.body.decls.add(fd) + } + if (let fd: FuncDecl <- parseDecl(cangjieLex(importSrc))) { + cd.body.decls.add(fd) + } + + return cd.toTokens() + } + throw ASTException("@JsonParent 只能标注在 class 声明上(不支持 struct/interface/enum)") +} + +/// 收集类体内的可序列化字段(_ 前缀 + 类型注解;跳过 @JsonIgnore;@JsonPropertyName 用注解名) +private func collectFields(cd: ClassDecl): ArrayList { + var result = ArrayList() + for (d in cd.body.decls) { + match (d) { + case vd: VarDecl => + if (isSerializableField(vd)) { + result.add(FieldInfo(vd.identifier.value, vd.identifier.value, + vd.declType.toTokens().toString(), vd.keyword.kind == TokenKind.VAR)) + } + case md: MacroExpandDecl => + // 带注解的字段:@JsonIgnore 跳过;@JsonPropertyName["x"] 取注解名 + if (md.identifier.value == "JsonIgnore") { + continue + } + if (let Some(inner) <- unwrapMacroField(md)) { + if (isSerializableField(inner)) { + let jsonName = if (md.identifier.value == "JsonPropertyName") { + // 属性 tokens 形如 "user_alias"(带引号),去掉两端引号 + stripQuotes(md.macroAttrs.toString()) + } else { + inner.identifier.value + } + result.add(FieldInfo(inner.identifier.value, jsonName, + inner.declType.toTokens().toString(), inner.keyword.kind == TokenKind.VAR)) + } + } + case _ => () + } + } + result +} + +/// 普通字段是否可序列化:_ 前缀 + 有类型注解 +private func isSerializableField(vd: VarDecl): Bool { + vd.identifier.value.startsWith("_") && !vd.colon.value.isEmpty() +} + +/// 从 MacroExpandDecl 提取内部 VarDecl(带注解的字段声明) +private func unwrapMacroField(md: MacroExpandDecl): ?VarDecl { + let visitor = FieldVisitor() + md.traverse(visitor) + visitor.getResult() +} + +/// 遍历器:在 MacroExpandDecl 内部查找 VarDecl +private class FieldVisitor <: Visitor { + private var _decl: ?VarDecl = None + + public override func visit(decl: VarDecl) { + _decl = Some(decl) + this.breakTraverse() + } + + public func getResult(): ?VarDecl { + _decl + } +} + +/// 去掉字符串两端引号(JsonPropertyName 属性 tokens 形如 "user_alias") +private func stripQuotes(s: String): String { + if (s.size >= 2 && s[0..1] == "\"" && s[s.size - 1..] == "\"") { + return s[1..s.size - 1] + } + s +} + +/// 生成 exportJsonFields 源码 +private func buildExportSource(className: String, fields: ArrayList): String { + var sb = StringBuilder() + sb.append(" public static func exportJsonFields(instance: ${className}): HashMap {\n") + sb.append(" let m = HashMap()\n") + for (f in fields) { + sb.append(" m[\"${f.jsonName}\"] = instance.${f.fieldName}\n") + } + sb.append(" return m\n") + sb.append(" }\n") + sb.toString() +} + +/// 生成 importJsonFields 源码 +private func buildImportSource(className: String, fields: ArrayList): String { + var sb = StringBuilder() + sb.append(" public static func importJsonFields(instance: ${className}, json: HashMap): Unit {\n") + sb.append(" for ((k, v) in json) {\n") + for (f in fields) { + if (!f.isVar) { + continue // let 字段不导入 + } + sb.append(" if (k == \"${f.jsonName}\") {\n") + sb.append(" if (let val: ${f.typeName} <- v) {\n") + sb.append(" instance.${f.fieldName} = val\n") + sb.append(" }\n") + sb.append(" }\n") + } + sb.append(" }\n") + sb.append(" }\n") + sb.toString() +}