From a7ed2902f5b0244d53ea5b82b786c1c533b59700 Mon Sep 17 00:00:00 2001 From: xRain Date: Mon, 17 Aug 2026 12:48:03 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E5=85=85=20soulsoft=20?= =?UTF-8?q?=E5=8F=82=E8=80=83=E5=AE=9E=E7=8E=B0=E5=88=86=E6=9E=90=E4=B8=8E?= =?UTF-8?q?=E8=B7=AF=E7=BA=BF=E5=AF=B9=E6=AF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §1.5 读源码总结 soulsoft_serialization 的做法:宏给每个类生成 4 个序列化方法 + superType 参数,继承靠编译期 super/静态方法调用链,零反射读字段 - §2 方案总览明确路线 A(入口不变+父类宏)为主方案 - §8 新增路线 A/B 取舍对比表,明确推荐路线 A 的理由: 保留 Serialize(obj)/Deserialize(json) 免标注 API,只借 soulsoft 「宏生成静态类型字段代码」技巧 --- docs/父类字段序列化方案.md | 66 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/docs/父类字段序列化方案.md b/docs/父类字段序列化方案.md index 85f7b23..17e357c 100644 --- a/docs/父类字段序列化方案.md +++ b/docs/父类字段序列化方案.md @@ -91,10 +91,49 @@ let result = f.apply(TypeInfo.of(), args) // ✓ 调用成功 原理:`exportJsonFields` 内部用**静态类型** `BaseUser` 访问 `_id`,走的是普通字段访问(非反射),完全合法;外部通过静态方法反射调用,`checkStaticDeclaringType` 只校验声明的类(用 `thisType` 参数),不校验实例的运行时类型。 +### 1.5 参考实现:soulsoft_serialization 是怎么做的(已读源码) + +soulsoft 走的路线与本方案不同——**它完全不用反射读字段**,而是宏给**每个类**生成序列化方法,运行时是普通方法调用链: + +```cangjie +@Serialization[superType: BaseUser] // 子类显式告诉宏:我的父类是 BaseUser +public class User <: BaseUser { ... } +``` + +宏在编译期(AST 层)为类生成 4 个方法 + 实现 `ISerialization` 接口: + +| 方法 | 可见性 | 作用 | +|---|---|---| +| `serializeObject(options): DataModel` | public | 序列化入口,构造 DataModelStruct 后调追加方法 | +| `serializeObject(dms, options): Unit` | protected | 追加本类字段(`dms.add(Field("_id", this._id.serializeObject(options)))`,**编译期静态访问**) | +| `deserializeObject(dm, options): T` | public static | 反序列化入口,构造实例后调追加方法 | +| `deserializeObject(dms, data, options): Unit` | protected static | 回填本类字段(`data._id = String.deserializeObject(...)`,**编译期静态赋值**) | + +**继承串联靠编译期方法调用,不是反射**: + +```cangjie +// 子类生成的方法,方法体第一行: +super.serializeObject(dms, options) // 序列化:父类字段由父类方法追加 +BaseUser.deserializeObject(dms, data, options) // 反序列化:先回填父类字段 +``` + +关键点: + +- 父类字段访问是宏生成的 `this._id` / `data._id = ...`,**静态类型访问**,天然绕开反射的声明类检查(与本方案 §2.2 同理); +- 运行时 `data.serializeObject(options)` 是接口虚方法调用、`T.deserializeObject(...)` 是泛型静态调用,全部编译期绑定,**零反射、零类型转换**; +- 宏怎么知道父类是谁?**用户显式传 `superType` 参数**——印证了「宏无法查询类型信息」的限制,soulsoft 用「让用户声明」解决; +- 代价:**每个类都要标宏**,且 API 带泛型约束 `where T <: ISerializable`——不标宏的类编译期就拒绝,「任意类免标注序列化」能力不存在。 + +**与本方案的关系**:soulsoft 的「宏生成静态类型字段代码」思路与本方案一致,可以借鉴;但它的「每个类标宏 + 泛型约束」形态会破坏 simapi_serialization「任意类免标注、`Serialize(obj)`/`Deserialize(json)`」的 API 契约。因此本方案**只借其「宏生成字段代码」的技巧**,保留自己的反射入口(详见 §8 路线对比)。 + --- ## 2. 方案总览 +> 本方案 = **路线 A**(推荐):保留 simapi_serialization 现有的反射入口 API, +> 仅对「想暴露父类字段」的父类用宏生成静态导出/导入方法,库沿继承链调用。 +> 路线 B(soulsoft 全宏风格)见 §8 对比,作为备选。 + ``` ┌─────────────────────────────────────────────────────────┐ │ 宏 @JsonExportFields(标注在父类上) │ @@ -121,6 +160,10 @@ let result = f.apply(TypeInfo.of(), args) // ✓ 调用成功 - 导出方法内部:`instance._id` 是静态类型字段访问(编译期解析),不是反射,无任何运行时检查; - 导出方法外部:通过**静态方法反射**调用,静态方法没有「实例声明类」校验,只校验声明类(用 `thisType` 参数),子类实例天然是父类类型的合法值。 +### 2.3 为什么不用 soulsoft 的「每个类标宏」形态 + +soulsoft 的 API 带泛型约束 `where T <: ISerializable`,不标宏的类在编译期就被拒绝——这要求**所有**序列化类都标注宏,与 simapi_serialization「任意类免标注、`Serialize(obj)`/`Deserialize(json)`」的契约冲突。本方案把「宏生成字段代码」与「反射入口」分层:宏只在父类上补「父类字段」这一块,其余照旧反射,二者互不干扰。 + --- ## 3. 宏设计 @@ -386,11 +429,30 @@ main() { --- -## 8. 备选方案对比 +## 8. 路线对比与备选方案 + +### 8.1 两条可行路线的取舍 + +| | 路线 A:入口不变 + 父类宏(本方案) | 路线 B:soulsoft 全宏风格 | +|---|---|---| +| 宏标在哪 | **仅父类**标 `@JsonExportFields` | **每个类**标 `@Serialization[superType: 父类]` | +| 继承串联 | 库运行时反射 `superClass` 链 + 静态方法调用 | 编译期 `super.serializeObject(...)` / `Parent.deserializeObject(...)` 调用链 | +| `Serialize(obj)` / `Deserialize(json)` | ✅ **完全不变**(无泛型约束,任意类) | 需保留反射回退,否则无宏类编译期拒绝 | +| 无宏的类 | ✅ 照旧全反射(向后兼容) | 无法序列化(除非入口加反射回退) | +| 调用方改动 | 无 | 每个类要加标注 + `superType` | +| 运行时开销 | 一次静态方法反射查找(可缓存) | 零反射,纯方法调用 | +| 类型安全 | 导出方法内静态访问(安全),返回值 `as` 转换(需兜底) | 全程编译期强类型 | +| 宏实现复杂度 | 中(解析字段生成 2 个静态方法) | 高(生成 4 方法 + 接口实现 + 构造器 + prop) | +| 字段约定 | 无(所有 public var) | 必须 `_` 前缀 + var + 类型注解(`isAllowedMemberVarDecl`) | + +**结论**:路线 B 的运行时性能更优,但要求每个类标宏、约束字段命名、改变 API 形态——与 simapi_serialization「任意类免标注」的核心价值冲突。**推荐路线 A**:只借 soulsoft「宏生成静态类型字段代码」的技巧,入口 API、调用方、无宏类行为全部保持现状。 + +### 8.2 备选方案总表 | 方案 | 可行性 | 改动量 | 说明 | |---|---|---|---| -| **宏标注父类 + 静态导出方法(本方案)** | ✅ 已验证 | 中 | 自动、向后兼容、无反射限制 | +| **路线 A:宏标注父类 + 静态导出方法(推荐)** | ✅ 已验证 | 中 | 自动、向后兼容、无反射限制、API 不变 | +| 路线 B:soulsoft 全宏风格(每类标宏 + superType) | ✅ 可参考 | 高 | 零反射,但破坏免标注 API、约束字段命名 | | 序列化库运行时遍历继承链 + 反射读父类字段 | ❌ | — | `getValue` 声明类严格校验,直接抛异常 | | 宏把父类字段复制到子类 | ❌ | — | 编译器禁止字段遮蔽 | | 用户在子类手写父类字段(复制粘贴) | ✅ | 小但人工 | 破坏继承语义、易漏改;仅作为临时 workaround |