refactor: 注解与宏改名——Serializer 前缀统一命名

- @JsonPropertyName → @SerializerPropertyName
- @JsonIgnore → @SerializerIgnore
- @JsonParent → @SerializerParent(宏文件 JsonParentMacro.cj → SerializerParentMacro.cj)
- 同步更新:JsonAnnotations、ReflectionCache、宏内部注解判断、
  测试、方案文档、serialization_test 独立项目
- 16 用例全绿
This commit is contained in:
2026-08-17 13:58:05 +08:00
parent affadac6c2
commit 990f10f98c
9 changed files with 77 additions and 77 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
[package] [package]
cjc-version = "1.1.3" cjc-version = "1.1.3"
name = "simapi_serialization" name = "simapi_serialization"
description = "SimApi 全反射 JSON 序列化库(对齐 .NET JsonSerializer.Serialize/Deserialize + JsonPropertyName/JsonIgnore 特性)" description = "SimApi 全反射 JSON 序列化库(对齐 .NET JsonSerializer.Serialize/Deserialize + SerializerPropertyName/SerializerIgnore 特性)"
version = "1.0.0" version = "1.0.0"
target-dir = "" target-dir = ""
output-type = "static" output-type = "static"
+23 -23
View File
@@ -136,7 +136,7 @@ BaseUser.deserializeObject(dms, data, options) // 反序列化:先回填父
``` ```
┌─────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────┐
│ 宏 @JsonParent(标注在父类上) │ │ 宏 @SerializerParent(标注在父类上) │
│ → 编译期解析父类声明的 var 字段 │ │ → 编译期解析父类声明的 var 字段 │
│ → 自动生成两个静态方法(静态类型访问,无反射检查): │ │ → 自动生成两个静态方法(静态类型访问,无反射检查): │
│ exportJsonFields(instance: Parent) │ │ exportJsonFields(instance: Parent) │
@@ -171,7 +171,7 @@ soulsoft 的 API 带泛型约束 `where T <: ISerializable`,不标宏的类在
### 3.1 宏定义 ### 3.1 宏定义
```cangjie ```cangjie
// 文件:src/json/JsonParentMacro.cj // 文件:src/json/SerializerParentMacro.cj
// 包:simapi_serialization.json(宏包需单独编译,见 3.3) // 包:simapi_serialization.json(宏包需单独编译,见 3.3)
macro package simapi_serialization.json macro package simapi_serialization.json
@@ -180,10 +180,10 @@ import std.ast.*
import std.collection.* import std.collection.*
/// 标注在父类上:自动生成父类字段的 JSON 导出/导入静态方法 /// 标注在父类上:自动生成父类字段的 JSON 导出/导入静态方法
public macro JsonParent(input: Tokens): Tokens { public macro SerializerParent(input: Tokens): Tokens {
// 1. 解析输入的 ClassDecl(非类声明 → 抛 MacroException // 1. 解析输入的 ClassDecl(非类声明 → 抛 MacroException
// 2. 校验 open:非 open class → 抛 MacroException(见 3.1.1 // 2. 校验 open:非 open class → 抛 MacroException(见 3.1.1
// 3. 收集 public var 字段(跳过 @JsonIgnore // 3. 收集 public var 字段(跳过 @SerializerIgnore
// 4. 生成 exportJsonFields / importJsonFields 两个静态方法 // 4. 生成 exportJsonFields / importJsonFields 两个静态方法
// 5. 拼回原类声明 + 新方法,返回 Tokens // 5. 拼回原类声明 + 新方法,返回 Tokens
} }
@@ -191,7 +191,7 @@ public macro JsonParent(input: Tokens): Tokens {
#### 3.1.1 open 处理(两种模式,默认自动补 open) #### 3.1.1 open 处理(两种模式,默认自动补 open)
`@JsonParent` 的语义是「暴露**父类**字段给子类继承链」——标注的类**必须可被继承**。Cangjie 类默认不可继承(非 open 类被继承会编译报错 `super class is not inheritable`),所以宏展开时处理 `open` `@SerializerParent` 的语义是「暴露**父类**字段给子类继承链」——标注的类**必须可被继承**。Cangjie 类默认不可继承(非 open 类被继承会编译报错 `super class is not inheritable`),所以宏展开时处理 `open`
```cangjie ```cangjie
/// 遍历类声明的修饰符,检查是否含 open(参考 soulsoft Extensions.cj isOpen /// 遍历类声明的修饰符,检查是否含 open(参考 soulsoft Extensions.cj isOpen
@@ -216,13 +216,13 @@ if (!isOpenClass(decl)) {
- **已实测可行**:宏给 `class Base` 自动加 `open` 后,子类 `class Child <: Base` 编译通过、正常运行(`macro_open_probe` 验证); - **已实测可行**:宏给 `class Base` 自动加 `open` 后,子类 `class Child <: Base` 编译通过、正常运行(`macro_open_probe` 验证);
- 宏展开的 `open` 在语义检查**之前**生效,所以后续继承检查能看到; - 宏展开的 `open` 在语义检查**之前**生效,所以后续继承检查能看到;
- 用户少写一个关键字,标注 `@JsonParent` 本身就表达了「我要被继承」的意图。 - 用户少写一个关键字,标注 `@SerializerParent` 本身就表达了「我要被继承」的意图。
**模式二:严格校验(不自动补,非 open 报错)** **模式二:严格校验(不自动补,非 open 报错)**
```cangjie ```cangjie
if (!isOpenClass(decl)) { if (!isOpenClass(decl)) {
throw ASTException("@JsonParent 只能标注在 open class 上," throw ASTException("@SerializerParent 只能标注在 open class 上,"
+ "因为非 open 类不能被继承,不存在子类场景。") + "因为非 open 类不能被继承,不存在子类场景。")
} }
``` ```
@@ -230,7 +230,7 @@ if (!isOpenClass(decl)) {
-`ASTException`(宏内实际可用异常类,soulsoft 同款)→ 编译期直接报错,错误定位到标注处(`main.cj:5:1` 形式); -`ASTException`(宏内实际可用异常类,soulsoft 同款)→ 编译期直接报错,错误定位到标注处(`main.cj:5:1` 形式);
- 顺带校验:标注在 **struct / interface / enum / func** 等非 class 声明上也抛错(`parseDecl` 结果 `as ClassDecl` 失败即报,已实测)。 - 顺带校验:标注在 **struct / interface / enum / func** 等非 class 声明上也抛错(`parseDecl` 结果 `as ClassDecl` 失败即报,已实测)。
**取舍**:自动补 open 更省事但「隐式改变类语义」;严格校验更显式但要求用户记得写 `open`。默认建议模式一(自动补),可在宏属性中提供开关,如 `@JsonParent[requireOpen: true]` 切到模式二。 **取舍**:自动补 open 更省事但「隐式改变类语义」;严格校验更显式但要求用户记得写 `open`。默认建议模式一(自动补),可在宏属性中提供开关,如 `@SerializerParent[requireOpen: true]` 切到模式二。
- 参考先例:soulsoft `Extensions.cj:75-82` 遍历 `modifiers``TokenKind.OPEN``SerializationMacro.cj:27``throw ASTException(...)` 做宏内校验;`cd.modifiers.add(Modifier(Token(TokenKind.OPEN)))` 与 soulsoft `funcDecl.modifiers.add(Modifier(Token(TokenKind.PUBLIC)))` 同款操作。 - 参考先例:soulsoft `Extensions.cj:75-82` 遍历 `modifiers``TokenKind.OPEN``SerializationMacro.cj:27``throw ASTException(...)` 做宏内校验;`cd.modifiers.add(Modifier(Token(TokenKind.OPEN)))` 与 soulsoft `funcDecl.modifiers.add(Modifier(Token(TokenKind.PUBLIC)))` 同款操作。
@@ -239,11 +239,11 @@ if (!isOpenClass(decl)) {
对如下父类: 对如下父类:
```cangjie ```cangjie
@JsonParent @SerializerParent
open class BaseUser { open class BaseUser {
public var _id: String = "" public var _id: String = ""
public var _createdAt: String = "" public var _createdAt: String = ""
@JsonIgnore @SerializerIgnore
public var _temp: String = "" public var _temp: String = ""
} }
``` ```
@@ -254,7 +254,7 @@ open class BaseUser {
open class BaseUser { open class BaseUser {
public var _id: String = "" public var _id: String = ""
public var _createdAt: String = "" public var _createdAt: String = ""
@JsonIgnore @SerializerIgnore
public var _temp: String = "" public var _temp: String = ""
/// 导出:字段名 → 值(静态类型访问) /// 导出:字段名 → 值(静态类型访问)
@@ -262,7 +262,7 @@ open class BaseUser {
var m = HashMap<String, Any>() var m = HashMap<String, Any>()
m["_id"] = instance._id m["_id"] = instance._id
m["_createdAt"] = instance._createdAt m["_createdAt"] = instance._createdAt
// _temp 被 @JsonIgnore 跳过 // _temp 被 @SerializerIgnore 跳过
m m
} }
@@ -318,8 +318,8 @@ Cangjie 宏需要两步编译(`cjc --compile-macro` 先编宏,再编使用
| 细节 | 处理 | | 细节 | 处理 |
|---|---| |---|---|
| 字段类型 | 用 `TypeInfo.get(字段类型名)` 在运行时做类型转换(import 侧) | | 字段类型 | 用 `TypeInfo.get(字段类型名)` 在运行时做类型转换(import 侧) |
| `@JsonPropertyName` | export 键用注解名;import 匹配也用注解名 | | `@SerializerPropertyName` | export 键用注解名;import 匹配也用注解名 |
| `@JsonIgnore` | 直接跳过,不生成导出/导入语句 | | `@SerializerIgnore` | 直接跳过,不生成导出/导入语句 |
| `let`(不可变)字段 | 只导出,不导入(导入侧跳过) | | `let`(不可变)字段 | 只导出,不导入(导入侧跳过) |
| 无字段 | 生成空方法(返回空 map / 空操作),保证约定方法一定存在 | | 无字段 | 生成空方法(返回空 map / 空操作),保证约定方法一定存在 |
| 命名冲突 | 若用户已手写同名静态方法,宏应报错或跳过(设计取舍:报错更安全) | | 命名冲突 | 若用户已手写同名静态方法,宏应报错或跳过(设计取舍:报错更安全) |
@@ -435,10 +435,10 @@ private static let _parentImportCache = HashMap<String, Bool>()
```cangjie ```cangjie
import simapi_serialization.* import simapi_serialization.*
import simapi_serialization.json.JsonParent // 引入宏 import simapi_serialization.json.SerializerParent // 引入宏
// 父类标注宏 // 父类标注宏
@JsonParent @SerializerParent
open class BaseUser { open class BaseUser {
public var _id: String = "" public var _id: String = ""
public var _createdAt: String = "" public var _createdAt: String = ""
@@ -474,7 +474,7 @@ main() {
| 用例 | 断言 | | 用例 | 断言 |
|---|---| |---|---|
| 父类字段序列化 | 输出包含 `_id`/`_createdAt`,且 `@JsonIgnore` 父类字段不输出 | | 父类字段序列化 | 输出包含 `_id`/`_createdAt`,且 `@SerializerIgnore` 父类字段不输出 |
| 父类字段反序列化 | `Deserialize<User>` 后父类字段值正确 | | 父类字段反序列化 | `Deserialize<User>` 后父类字段值正确 |
| 多层继承 | GrandParent → Parent → Child 三层字段全部输出 | | 多层继承 | GrandParent → Parent → Child 三层字段全部输出 |
| 中间层无宏 | 三层链中 Parent 不标宏:`_gp` 仍导出,`_p` 不导出(逐层跳过) | | 中间层无宏 | 三层链中 Parent 不标宏:`_gp` 仍导出,`_p` 不导出(逐层跳过) |
@@ -482,11 +482,11 @@ main() {
| 子类字段与父类字段重名 | 子类优先(输出一份,值取子类的) | | 子类字段与父类字段重名 | 子类优先(输出一份,值取子类的) |
| 父类嵌套对象字段 | 父类字段值是对象时正确递归 | | 父类嵌套对象字段 | 父类字段值是对象时正确递归 |
| 父类字段 null | 导入时保持默认值 | | 父类字段 null | 导入时保持默认值 |
| 与 @JsonPropertyName 组合 | 父类字段用注解名输出/匹配 | | 与 @SerializerPropertyName 组合 | 父类字段用注解名输出/匹配 |
| 顶层序列化父类类型实例 | `Serialize(BaseUser 实例)` 直接可用 | | 顶层序列化父类类型实例 | `Serialize(BaseUser 实例)` 直接可用 |
| **宏自动补 open** | `@JsonParent class Base`(无 open)展开后子类可继承(编译通过) | | **宏自动补 open** | `@SerializerParent class Base`(无 open)展开后子类可继承(编译通过) |
| **宏严格模式报错** | `@JsonParent[requireOpen: true]` 标注非 open class 编译失败 | | **宏严格模式报错** | `@SerializerParent[requireOpen: true]` 标注非 open class 编译失败 |
| **宏非 class 标注报错** | 对 struct/interface/enum 标注 `@JsonParent` 编译失败 | | **宏非 class 标注报错** | 对 struct/interface/enum 标注 `@SerializerParent` 编译失败 |
--- ---
@@ -508,7 +508,7 @@ main() {
| | 路线 A:入口不变 + 父类宏(本方案) | 路线 B:soulsoft 全宏风格 | | | 路线 A:入口不变 + 父类宏(本方案) | 路线 B:soulsoft 全宏风格 |
|---|---|---| |---|---|---|
| 宏标在哪 | **仅父类**标 `@JsonParent` | **每个类**标 `@Serialization[superType: 父类]` | | 宏标在哪 | **仅父类**标 `@SerializerParent` | **每个类**标 `@Serialization[superType: 父类]` |
| 继承串联 | 库运行时反射 `superClass` 链 + 静态方法调用 | 编译期 `super.serializeObject(...)` / `Parent.deserializeObject(...)` 调用链 | | 继承串联 | 库运行时反射 `superClass` 链 + 静态方法调用 | 编译期 `super.serializeObject(...)` / `Parent.deserializeObject(...)` 调用链 |
| `Serialize(obj)` / `Deserialize<T>(json)` | ✅ **完全不变**(无泛型约束,任意类) | 需保留反射回退,否则无宏类编译期拒绝 | | `Serialize(obj)` / `Deserialize<T>(json)` | ✅ **完全不变**(无泛型约束,任意类) | 需保留反射回退,否则无宏类编译期拒绝 |
| 无宏的类 | ✅ 照旧全反射(向后兼容) | 无法序列化(除非入口加反射回退) | | 无宏的类 | ✅ 照旧全反射(向后兼容) | 无法序列化(除非入口加反射回退) |
@@ -535,7 +535,7 @@ main() {
## 9. 实施步骤(评审通过后) ## 9. 实施步骤(评审通过后)
1. 新建宏文件 `src/json/JsonParentMacro.cj`(或独立宏包),实现 `JsonParent` 宏; 1. 新建宏文件 `src/json/SerializerParentMacro.cj`(或独立宏包),实现 `SerializerParent` 宏;
2. 验证 cjpm 对宏包的编译流程(风险 #1),必要时调整项目结构; 2. 验证 cjpm 对宏包的编译流程(风险 #1),必要时调整项目结构;
3.`JsonWriter.writeObject`superClass 链调用 `exportJsonFields` 3.`JsonWriter.writeObject`superClass 链调用 `exportJsonFields`
4.`JsonReader.readObject`superClass 链调用 `importJsonFields` 4.`JsonReader.readObject`superClass 链调用 `importJsonFields`
+1 -1
View File
@@ -4,7 +4,7 @@
* simapi_serialization 根包锚点(cjpm 要求 src 根目录至少有一个 .cj 文件,否则不扫描子目录)。 * simapi_serialization 根包锚点(cjpm 要求 src 根目录至少有一个 .cj 文件,否则不扫描子目录)。
* 全部实现位于子包 simapi_serialization.json(对应 .NET System.Text.Json)。 * 全部实现位于子包 simapi_serialization.json(对应 .NET System.Text.Json)。
* 通过 public import 重导出:`import simapi_serialization.*` 仍可直接使用 * 通过 public import 重导出:`import simapi_serialization.*` 仍可直接使用
* JsonSerializer / JsonOption / @JsonPropertyName / @JsonIgnore 等。 * JsonSerializer / JsonOption / @SerializerPropertyName / @SerializerIgnore 等。
*/ */
package simapi_serialization package simapi_serialization
+8 -8
View File
@@ -1,8 +1,8 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 对齐 .NET System.Text.Json.Serialization 特性: * 对齐 .NET System.Text.Json.Serialization 特性:
* - [JsonPropertyName("xxx")] → @JsonPropertyName["xxx"] 指定字段的 JSON 名称 * - [SerializerPropertyName("xxx")] → @SerializerPropertyName["xxx"] 指定字段的 JSON 名称
* - [JsonIgnore] → @JsonIgnore 序列化/反序列化时忽略该字段 * - [SerializerIgnore] → @SerializerIgnore 序列化/反序列化时忽略该字段
* *
* 设计决策:本库序列化/反序列化只处理「var 成员变量」(std.reflect 的 instanceVariables), * 设计决策:本库序列化/反序列化只处理「var 成员变量」(std.reflect 的 instanceVariables),
* 不处理 prop 属性(instanceProperties)。因此注解 target 仅限 MemberVariable * 不处理 prop 属性(instanceProperties)。因此注解 target 仅限 MemberVariable
@@ -15,11 +15,11 @@
package simapi_serialization.json package simapi_serialization.json
/** /**
* 指定字段的 JSON 名称(对齐 .NET JsonPropertyNameAttribute)。 * 指定字段的 JSON 名称(对齐 .NET SerializerPropertyNameAttribute)。
* 用法:@JsonPropertyName["user_name"] public var _username: String = "" * 用法:@SerializerPropertyName["user_name"] public var _username: String = ""
*/ */
@Annotation[target: [MemberVariable]] @Annotation[target: [MemberVariable]]
public class JsonPropertyName { public class SerializerPropertyName {
public let name: String public let name: String
public const init(name: String) { public const init(name: String) {
@@ -28,10 +28,10 @@ public class JsonPropertyName {
} }
/** /**
* 标记字段不做 JSON 处理(对齐 .NET JsonIgnoreAttribute)。 * 标记字段不做 JSON 处理(对齐 .NET SerializerIgnoreAttribute)。
* 用法:@JsonIgnore public var _temp: String = "" * 用法:@SerializerIgnore public var _temp: String = ""
*/ */
@Annotation[target: [MemberVariable]] @Annotation[target: [MemberVariable]]
public class JsonIgnore { public class SerializerIgnore {
public const init() {} public const init() {}
} }
+1 -1
View File
@@ -133,7 +133,7 @@ public class JsonReader {
case None => () case None => ()
} }
} }
// 父类字段(@JsonParent 宏生成的静态导入方法,沿继承链逐层回填) // 父类字段(@SerializerParent 宏生成的静态导入方法,沿继承链逐层回填)
readParentFields(value, ct, instance, options) readParentFields(value, ct, instance, options)
instance instance
} }
+18 -18
View File
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* JsonSerializer 单元测试(cjpm test)。 * JsonSerializer 单元测试(cjpm test)。
* 覆盖:基础类型 / 对象字段输出 / 注解(JsonPropertyName、JsonIgnore/ Option / * 覆盖:基础类型 / 对象字段输出 / 注解(SerializerPropertyName、SerializerIgnore/ Option /
* 集合(Array、ArrayList、HashSet/ HashMap / 枚举 / 命名策略 / * 集合(Array、ArrayList、HashSet/ HashMap / 枚举 / 命名策略 /
* 继承(平台限制)/ maxDepth / 宽松类型转换。 * 继承(平台限制)/ maxDepth / 宽松类型转换。
* *
@@ -27,15 +27,15 @@ class Address {
public var zip: String = "" // 无下划线 → 原样输出 zip public var zip: String = "" // 无下划线 → 原样输出 zip
} }
// 继承测试:父类标注 @JsonParent 宏 → 自动补 open + 生成导出/导入静态方法, // 继承测试:父类标注 @SerializerParent 宏 → 自动补 open + 生成导出/导入静态方法,
// 父类字段(_id/_createdAt)经宏的静态类型访问绕开反射声明类校验 // 父类字段(_id/_createdAt)经宏的静态类型访问绕开反射声明类校验
@JsonParent @SerializerParent
open class BaseUser { open class BaseUser {
public var _id: String = "" public var _id: String = ""
public var _createdAt: String = "" public var _createdAt: String = ""
@JsonIgnore @SerializerIgnore
public var _temp: String = "" public var _temp: String = ""
@JsonPropertyName["parent_alias"] @SerializerPropertyName["parent_alias"]
public var _parentAlias: String = "" public var _parentAlias: String = ""
} }
@@ -49,14 +49,14 @@ class User <: BaseUser {
public var remark: ?String = None // 无下划线(Option=None → null public var remark: ?String = None // 无下划线(Option=None → null
public var _tags: Array<String> = [] // 下划线字段(集合) public var _tags: Array<String> = [] // 下划线字段(集合)
public var addr: Address = Address() // 无下划线(嵌套对象) public var addr: Address = Address() // 无下划线(嵌套对象)
@JsonPropertyName["user_alias"] @SerializerPropertyName["user_alias"]
public var _alias: String = "" // 下划线 + 注解覆盖 public var _alias: String = "" // 下划线 + 注解覆盖
@JsonIgnore @SerializerIgnore
public var temp: String = "secret" // 无下划线 + 忽略 public var temp: String = "secret" // 无下划线 + 忽略
} }
// 多层继承:爷(标宏)→ 父(不标宏)→ 子 // 多层继承:爷(标宏)→ 父(不标宏)→ 子
@JsonParent @SerializerParent
open class GrandParent { open class GrandParent {
public var _gp: String = "" public var _gp: String = ""
} }
@@ -129,10 +129,10 @@ public class JsonSerializerTests {
// 无下划线字段原样输出 // 无下划线字段原样输出
@Expect(json.contains("\"score\""), true) @Expect(json.contains("\"score\""), true)
@Expect(json.contains("\"active\": true"), true) @Expect(json.contains("\"active\": true"), true)
// @JsonPropertyName 覆盖字段名 // @SerializerPropertyName 覆盖字段名
@Expect(json.contains("\"user_alias\": \"xiaoming\""), true) @Expect(json.contains("\"user_alias\": \"xiaoming\""), true)
@Expect(json.contains("\"_alias\""), false) @Expect(json.contains("\"_alias\""), false)
// @JsonIgnore 字段不输出 // @SerializerIgnore 字段不输出
@Expect(json.contains("temp"), false) @Expect(json.contains("temp"), false)
@Expect(json.contains("\"secret\""), false) @Expect(json.contains("\"secret\""), false)
// OptionSome→值,None→null // OptionSome→值,None→null
@@ -143,13 +143,13 @@ public class JsonSerializerTests {
// 嵌套对象 // 嵌套对象
@Expect(json.contains("\"_city\": \"beijing\""), true) @Expect(json.contains("\"_city\": \"beijing\""), true)
@Expect(json.contains("\"zip\": \"100000\""), true) @Expect(json.contains("\"zip\": \"100000\""), true)
// 父类字段(@JsonParent 宏):_id/_createdAt 输出,@JsonIgnore 父类字段不输出 // 父类字段(@SerializerParent 宏):_id/_createdAt 输出,@SerializerIgnore 父类字段不输出
@Expect(json.contains("\"_id\": \"u-1\""), true) @Expect(json.contains("\"_id\": \"u-1\""), true)
@Expect(json.contains("\"_createdAt\": \"2025-01-01\""), true) @Expect(json.contains("\"_createdAt\": \"2025-01-01\""), true)
// 父类 @JsonPropertyName["parent_alias"] 生效 // 父类 @SerializerPropertyName["parent_alias"] 生效
@Expect(json.contains("\"parent_alias\": \"parent-xiaoming\""), true) @Expect(json.contains("\"parent_alias\": \"parent-xiaoming\""), true)
@Expect(json.contains("_parentAlias"), false) @Expect(json.contains("_parentAlias"), false)
// 父类 @JsonIgnore _temp 不输出 // 父类 @SerializerIgnore _temp 不输出
@Expect(json.contains("secret-parent"), false) @Expect(json.contains("secret-parent"), false)
} }
@@ -165,8 +165,8 @@ public class JsonSerializerTests {
@Expect(u2.addr._city, "beijing") @Expect(u2.addr._city, "beijing")
@Expect(u2.addr.zip, "100000") @Expect(u2.addr.zip, "100000")
@Expect(u2._alias, "xiaoming") @Expect(u2._alias, "xiaoming")
@Expect(u2.temp, "secret") // @JsonIgnore:反序列化不改动默认值 @Expect(u2.temp, "secret") // @SerializerIgnore:反序列化不改动默认值
// 父类字段往返(@JsonParent 宏) // 父类字段往返(@SerializerParent 宏)
@Expect(u2._id, "u-1") @Expect(u2._id, "u-1")
@Expect(u2._createdAt, "2025-01-01") @Expect(u2._createdAt, "2025-01-01")
@Expect(u2._parentAlias, "parent-xiaoming") @Expect(u2._parentAlias, "parent-xiaoming")
@@ -322,7 +322,7 @@ public class JsonSerializerTests {
@Expect(threw, true) @Expect(threw, true)
} }
/// 父类字段序列化/反序列化(@JsonParent 宏):已并入 objectSerialize/objectRoundTrip /// 父类字段序列化/反序列化(@SerializerParent 宏):已并入 objectSerialize/objectRoundTrip
/// 此处验证宏生成的静态方法可直接调用 /// 此处验证宏生成的静态方法可直接调用
@TestCase @TestCase
public func parentMacroMethods() { public func parentMacroMethods() {
@@ -335,7 +335,7 @@ public class JsonSerializerTests {
@Expect(toStr(m["_id"]), "m-1") @Expect(toStr(m["_id"]), "m-1")
@Expect(toStr(m["_createdAt"]), "2025-02-02") @Expect(toStr(m["_createdAt"]), "2025-02-02")
@Expect(toStr(m["parent_alias"]), "alias-m") @Expect(toStr(m["parent_alias"]), "alias-m")
// @JsonIgnore 父类字段不导出 // @SerializerIgnore 父类字段不导出
@Expect(m.contains("_temp"), false) @Expect(m.contains("_temp"), false)
// 宏生成的导入方法 // 宏生成的导入方法
var json = HashMap<String, Any>() var json = HashMap<String, Any>()
@@ -366,7 +366,7 @@ public class JsonSerializerTests {
@Expect(back._mp, "") // 中间层字段保持默认值 @Expect(back._mp, "") // 中间层字段保持默认值
} }
/// 宏自动补 openGrandParent 未显式写 open@JsonParent 自动补),继承链可正常编译 /// 宏自动补 openGrandParent 未显式写 open@SerializerParent 自动补),继承链可正常编译
@TestCase @TestCase
public func parentAutoOpen() { public func parentAutoOpen() {
let leaf = LeafChild() let leaf = LeafChild()
+2 -2
View File
@@ -106,14 +106,14 @@ public class JsonWriter {
// 1) 子类自身字段(反射) // 1) 子类自身字段(反射)
let fields = ReflectionCache.getFields(ct, options) let fields = ReflectionCache.getFields(ct, options)
for (f in fields) { for (f in fields) {
// 只要不是 @JsonIgnore 的字段一律输出;None 字段输出 null // 只要不是 @SerializerIgnore 的字段一律输出;None 字段输出 null
if (f.ignore) { if (f.ignore) {
continue continue
} }
let raw = f.variable.getOrThrow().getValue(value) let raw = f.variable.getOrThrow().getValue(value)
obj.put(f.jsonName, writeValue(raw, options, depth + 1)) obj.put(f.jsonName, writeValue(raw, options, depth + 1))
} }
// 2) 父类字段(@JsonParent 宏生成的静态导出方法,沿继承链逐层调用) // 2) 父类字段(@SerializerParent 宏生成的静态导出方法,沿继承链逐层调用)
writeParentFields(value, ct, obj, options, depth) writeParentFields(value, ct, obj, options, depth)
obj obj
} }
+11 -11
View File
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* ReflectionCache:字段元数据(含继承链、命名策略、@JsonPropertyName/@JsonIgnore)与缓存。 * ReflectionCache:字段元数据(含继承链、命名策略、@SerializerPropertyName/@SerializerIgnore)与缓存。
* *
* 已知限制(平台级): * 已知限制(平台级):
* - InstanceVariableInfo.getValue/setValue 有「声明类严格类型校验」,父类字段无法用子类实例读写, * - InstanceVariableInfo.getValue/setValue 有「声明类严格类型校验」,父类字段无法用子类实例读写,
@@ -18,9 +18,9 @@ import std.reflect.*
public class FieldMetadata { public class FieldMetadata {
/// 反射字段名(如 _name /// 反射字段名(如 _name
public var name: String = "" public var name: String = ""
/// JSON 名称(@JsonPropertyName 优先,否则按命名策略) /// JSON 名称(@SerializerPropertyName 优先,否则按命名策略)
public var jsonName: String = "" public var jsonName: String = ""
/// 是否忽略(@JsonIgnore /// 是否忽略(@SerializerIgnore
public var ignore: Bool = false public var ignore: Bool = false
/// 字段类型 /// 字段类型
public var typeInfo: ?TypeInfo = None public var typeInfo: ?TypeInfo = None
@@ -40,20 +40,20 @@ public class ReflectionCache {
private static let _cache = HashMap<String, ArrayList<FieldMetadata>>() private static let _cache = HashMap<String, ArrayList<FieldMetadata>>()
/// 类型 → 是否有 @JsonParent 宏生成的 exportJsonFieldsBool /// 类型 → 是否有 @SerializerParent 宏生成的 exportJsonFieldsBool
private static let _exportCache = HashMap<String, Bool>() private static let _exportCache = HashMap<String, Bool>()
/// 类型 → 是否有 @JsonParent 宏生成的 importJsonFieldsBool /// 类型 → 是否有 @SerializerParent 宏生成的 importJsonFieldsBool
private static let _importCache = HashMap<String, Bool>() private static let _importCache = HashMap<String, Bool>()
/** /**
* 该类型是否含 @JsonParent 宏生成的导出静态方法(含缓存)。 * 该类型是否含 @SerializerParent 宏生成的导出静态方法(含缓存)。
*/ */
public static func hasExportFields(typeInfo: ClassTypeInfo): Bool { public static func hasExportFields(typeInfo: ClassTypeInfo): Bool {
hasParentMethod(typeInfo, "exportJsonFields", _exportCache) hasParentMethod(typeInfo, "exportJsonFields", _exportCache)
} }
/** /**
* 该类型是否含 @JsonParent 宏生成的导入静态方法(含缓存)。 * 该类型是否含 @SerializerParent 宏生成的导入静态方法(含缓存)。
*/ */
public static func hasImportFields(typeInfo: ClassTypeInfo): Bool { public static func hasImportFields(typeInfo: ClassTypeInfo): Bool {
hasParentMethod(typeInfo, "importJsonFields", _importCache) hasParentMethod(typeInfo, "importJsonFields", _importCache)
@@ -96,13 +96,13 @@ public class ReflectionCache {
meta.variable = Some(v) meta.variable = Some(v)
meta.typeInfo = Some(v.typeInfo) meta.typeInfo = Some(v.typeInfo)
meta.mutable = v.isMutable() meta.mutable = v.isMutable()
// @JsonIgnore // @SerializerIgnore
if (v.findAnnotation<JsonIgnore>().isSome()) { if (v.findAnnotation<SerializerIgnore>().isSome()) {
meta.ignore = true meta.ignore = true
} }
// @JsonPropertyName 优先 // @SerializerPropertyName 优先
var jsonName = "" var jsonName = ""
if (let Some(jsonProp) <- v.findAnnotation<JsonPropertyName>()) { if (let Some(jsonProp) <- v.findAnnotation<SerializerPropertyName>()) {
jsonName = jsonProp.name jsonName = jsonProp.name
} else { } else {
jsonName = applyNamingPolicy(v.name, options.propertyNamingPolicy) jsonName = applyNamingPolicy(v.name, options.propertyNamingPolicy)
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* @JsonParent:标注在父类上,自动生成父类字段的 JSON 导出/导入静态方法。 * @SerializerParent:标注在父类上,自动生成父类字段的 JSON 导出/导入静态方法。
* *
* 背景:Cangjie 反射对实例成员的读写有「声明类严格校验」 * 背景:Cangjie 反射对实例成员的读写有「声明类严格校验」
* declaringClass != TypeInfo.of(instance)),父类字段无法用子类实例 * declaringClass != TypeInfo.of(instance)),父类字段无法用子类实例
@@ -9,12 +9,12 @@
* 调用约定静态方法(见 JsonWriter/JsonReader)。 * 调用约定静态方法(见 JsonWriter/JsonReader)。
* *
* 用法: * 用法:
* @JsonParent * @SerializerParent
* class BaseUser { * class BaseUser {
* public var _id: String = "" * public var _id: String = ""
* @JsonPropertyName["userId"] * @SerializerPropertyName["userId"]
* public var _userId: String = "" * public var _userId: String = ""
* @JsonIgnore * @SerializerIgnore
* public var _temp: String = "" * public var _temp: String = ""
* } * }
* *
@@ -22,7 +22,7 @@
* - 非 open class 自动补 open * - 非 open class 自动补 open
* - 生成 exportJsonFields(instance: T): HashMap<String, Any> * - 生成 exportJsonFields(instance: T): HashMap<String, Any>
* - 生成 importJsonFields(instance: T, json: HashMap<String, Any>): Unit * - 生成 importJsonFields(instance: T, json: HashMap<String, Any>): Unit
* - @JsonIgnore 字段跳过;@JsonPropertyName 用注解名做 JSON 键 * - @SerializerIgnore 字段跳过;@SerializerPropertyName 用注解名做 JSON 键
*/ */
macro package simapi_serialization.macros macro package simapi_serialization.macros
@@ -52,7 +52,7 @@ private class FieldInfo {
* @param input 被标注的类声明。 * @param input 被标注的类声明。
* @return 注入静态方法后的类声明。 * @return 注入静态方法后的类声明。
*/ */
public macro JsonParent(input: Tokens): Tokens { public macro SerializerParent(input: Tokens): Tokens {
let decl = parseDecl(input) let decl = parseDecl(input)
if (let cd: ClassDecl <- decl) { if (let cd: ClassDecl <- decl) {
// 1. open 处理:自动补 open(类被标注即表达「要被继承」的意图) // 1. open 处理:自动补 open(类被标注即表达「要被继承」的意图)
@@ -82,10 +82,10 @@ public macro JsonParent(input: Tokens): Tokens {
return cd.toTokens() return cd.toTokens()
} }
throw ASTException("@JsonParent 只能标注在 class 声明上(不支持 struct/interface/enum") throw ASTException("@SerializerParent 只能标注在 class 声明上(不支持 struct/interface/enum")
} }
/// 收集类体内的可序列化字段(_ 前缀 + 类型注解;跳过 @JsonIgnore@JsonPropertyName 用注解名) /// 收集类体内的可序列化字段(_ 前缀 + 类型注解;跳过 @SerializerIgnore@SerializerPropertyName 用注解名)
private func collectFields(cd: ClassDecl): ArrayList<FieldInfo> { private func collectFields(cd: ClassDecl): ArrayList<FieldInfo> {
var result = ArrayList<FieldInfo>() var result = ArrayList<FieldInfo>()
for (d in cd.body.decls) { for (d in cd.body.decls) {
@@ -96,13 +96,13 @@ private func collectFields(cd: ClassDecl): ArrayList<FieldInfo> {
vd.declType.toTokens().toString(), vd.keyword.kind == TokenKind.VAR)) vd.declType.toTokens().toString(), vd.keyword.kind == TokenKind.VAR))
} }
case md: MacroExpandDecl => case md: MacroExpandDecl =>
// 带注解的字段:@JsonIgnore 跳过;@JsonPropertyName["x"] 取注解名 // 带注解的字段:@SerializerIgnore 跳过;@SerializerPropertyName["x"] 取注解名
if (md.identifier.value == "JsonIgnore") { if (md.identifier.value == "SerializerIgnore") {
continue continue
} }
if (let Some(inner) <- unwrapMacroField(md)) { if (let Some(inner) <- unwrapMacroField(md)) {
if (isSerializableField(inner)) { if (isSerializableField(inner)) {
let jsonName = if (md.identifier.value == "JsonPropertyName") { let jsonName = if (md.identifier.value == "SerializerPropertyName") {
// 属性 tokens 形如 "user_alias"(带引号),去掉两端引号 // 属性 tokens 形如 "user_alias"(带引号),去掉两端引号
stripQuotes(md.macroAttrs.toString()) stripQuotes(md.macroAttrs.toString())
} else { } else {
@@ -144,7 +144,7 @@ private class FieldVisitor <: Visitor {
} }
} }
/// 去掉字符串两端引号(JsonPropertyName 属性 tokens 形如 "user_alias" /// 去掉字符串两端引号(SerializerPropertyName 属性 tokens 形如 "user_alias"
private func stripQuotes(s: String): String { private func stripQuotes(s: String): String {
if (s.size >= 2 && s[0..1] == "\"" && s[s.size - 1..] == "\"") { if (s.size >= 2 && s[0..1] == "\"" && s[s.size - 1..] == "\"") {
return s[1..s.size - 1] return s[1..s.size - 1]