# serialization(simcu::serialization) SimApi 全反射 JSON 序列化库(仓颉版),对齐 .NET `System.Text.Json` 的用法与注解风格。 - **免标注**:任意类(无参构造 + `var` 字段)无需实现接口、无需标注,开箱即用 - **注解**:`@PropertyName` / `@Ignore` / `@NotRequired`(对齐 `[JsonPropertyName]` / `[JsonIgnore]`,默认必填、`@NotRequired` 允许缺失) - **默认必填**:反序列化时要求每个 `var` 字段的键必须存在;标注 `@NotRequired` 的字段允许缺失(保持默认值) - **继承支持**:`@SerializerParent` 宏解决父类字段序列化(Cangjie 反射限制) - **API 对齐**:`JsonSerializer.serialize(obj, JsonOption)` / `deserialize(json, JsonOption)` - **字段名**:`_` 前缀与无下划线字段均支持(`_name` 原样输出 `_name`,`name` 输出 `name`) ## 快速开始 ### 引入 两种方式任选其一: **方式一:中央仓**(需先 `cjpm publish` 发布 `simcu::serialization`) ```toml [dependencies] "simcu::serialization" = "1.2.1" ``` **方式二:Git 仓库** ```toml [dependencies] "simcu::serialization" = { git = "https://gitcode.com/simcu/serialization-cj.git", version = "1.2.1" } ``` > 本地开发也可用 path 依赖:`"simcu::serialization" = { path = "../simapi-serialization" }` ```cangjie import simcu::serialization.* // 一个 import 全量可用(注解 + 序列化器) ``` ### 序列化 / 反序列化 ```cangjie class User { public var _name: String = "" public var _age: Int64 = 0 public var active: Bool = true public var _role: Role = Role.Member // 枚举 → 名字字符串 public var opt: ?String = None // Option:Some→值,None→null public var _tags: Array = [] public var addr: Address = Address() // 嵌套对象 } let u = User() u._name = "alice" u._age = 30 let json = JsonSerializer.serialize(u) // { "_name": "alice", "_age": 30, "active": true, "_role": "Member", "opt": null, "_tags": [], "addr": {...} } let u2 = JsonSerializer.deserialize(json) // 全部字段回填 ``` > 类无需显式 `public init() {}`——编译器自动提供无参构造(反射构造已验证)。 ### 注解 ```cangjie class User { @PropertyName["user_name"] public var _username: String = "" // JSON 键用 user_name(对齐 [JsonPropertyName]) @Ignore public var _temp: String = "" // 序列化/反序列化忽略(对齐 [JsonIgnore]) @NotRequired public var _nickname: String = "" // 反序列化时该键允许缺失(默认所有字段必填) public var _id: String = "" // 默认必填:反序列化时键必须存在,否则抛异常 } ``` > 注解类构造器**必须写 `public const init() {}`**(`@Annotation` 强制 const 构造,`@Ignore`/`@NotRequired` 这类无参注解也不例外);缺省/非 const 都会编译报错。 #### @PropertyName `字段名 → JSON 键名`,序列化与反序列化双向生效(对齐 `[JsonPropertyName]`)。 ```cangjie @PropertyName["user_name"] public var _username: String = "" ``` #### @Ignore 序列化/反序列化时跳过该字段(对齐 `[JsonIgnore]`)。 ```cangjie @Ignore public var _temp: String = "" ``` #### 默认必填 / @NotRequired **默认所有 `var` 字段必填**:反序列化时要求 JSON 对象**必须包含每个 `var` 字段的键**, 否则抛异常。值可为 `null`(`null` 保持字段默认值,不抛异常);只有**键缺失**才抛异常。 仅对反序列化生效,序列化侧不受影响。 标注 `@NotRequired` 的字段允许键缺失(缺失时保持字段默认值,不抛异常): ```cangjie @NotRequired public var _nickname: String = "" ``` - 普通类:`JsonSerializer.deserialize(json)` 在 `readObject` 阶段检测,必填键缺失抛异常。 - `@SerializerParent` 父类:宏生成的 `importJsonFields` 检测父类必填键,缺失抛异常并沿继承链上抛。 ## 父类字段序列化(@SerializerParent) ### 背景 Cangjie 反射对实例成员读写有**声明类严格校验**(`declaringClass != TypeInfo.of(instance)`), 父类字段无法用子类实例读写。`@SerializerParent` 宏在编译期生成静态导出/导入方法, 方法体内用静态类型访问字段,绕开该限制。 ### 用法 ```cangjie import simcu::serialization.* import simcu::serialization.macros.* @SerializerParent // 标注在父类上;非 open 类自动补 open class BaseUser { public var _id: String = "" public var _createdAt: String = "" } class User <: BaseUser { // 子类零改动 public var _name: String = "" } let u = User() u._id = "u-1" u._name = "alice" let json = JsonSerializer.serialize(u) // 输出包含父类字段:{ "_name": "alice", "_id": "u-1", "_createdAt": "" } ``` > 宏支持**任意字段名**(`_` 前缀或无下划线均可),只需有类型注解。 ### 特性 | 特性 | 说明 | |---|---| | 自动补 `open` | 非 open 类标注宏后自动可继承 | | 多层继承 | 沿 `superClass` 链逐层调用;中间层无宏自动跳过 | | 注解生效 | 父类字段的 `@PropertyName` / `@Ignore` / `@NotRequired` 同样生效 | | 任意字段名 | `_` 前缀与无下划线字段都处理 | | 向后兼容 | 无宏的类行为不变 | ### 宏生成的方法 ```cangjie // 宏为父类自动生成(可直接调用): BaseUser.exportJsonFields(instance) // HashMap BaseUser.importJsonFields(instance, json) // 回填字段 ``` ## 选项(JsonOption) | 选项 | 默认 | 说明 | |---|---|---| | `propertyNamingPolicy` | `CamelCase` | 字段命名策略 | | `enumAsString` | `true` | 枚举序列化为名字 | | `maxDepth` | `64` | 递归深度上限(超过抛异常) | ```cangjie let opt = JsonOption() opt.propertyNamingPolicy = PropertyNamingPolicy.SnakeCase // userName → user_name opt.maxDepth = 16 let json = JsonSerializer.serialize(u, opt) ``` ### 运行时类型反序列化(框架绑定用) 供框架模型绑定等场景按运行时类型反序列化(无需编译期泛型): ```cangjie import std.reflect.* let typeInfo = TypeInfo.of() let obj: Any = JsonSerializer.deserialize(typeInfo, jsonString) ``` ### 命名策略(PropertyNamingPolicy) | 策略 | 效果 | |---|---| | `Keep` | 字段名原样(`_username` → `_username`) | | `CamelCase` | 首字母小写(`Name` → `name`;下划线字段保持原样) | | `SnakeCase` | 大写转下划线(`userName` → `user_name`) | > 注:不剥除字段名前导下划线——`_name` 原样输出 `_name`。 ## 支持的类型 | 类别 | 说明 | |---|---| | 基础类型 | `String` / `Bool` / 全部整数 / 浮点 / `Rune` | | 集合 | `Array` / `ArrayList` / `HashSet` | | 字典 | `HashMap`(键须可转字符串) | | `Option` | `Some(x)` → 值;`None` → `null` | | 枚举 | 构造器名(`Role.Admin` → `"Admin"`) | | 普通类 | 无参构造 + `var` 字段;嵌套对象递归 | ### 宽松类型转换(反序列化) JSON 数字 ↔ 字符串字段自动互转:`deserialize("42")` → `"42"`, `deserialize("\"42\"")` → `42`。 ## 已知限制 - 父类字段序列化需父类标注 `@SerializerParent`(见上文;Cangjie 反射平台限制) - 反序列化要求目标类型**无参构造**(编译器自动提供,无需显式写 `init(){}`)、字段为 `var`(`let` 只读字段跳过) - 集合/字典元素类型需为支持的类型 - 泛型父类暂不支持 ## 包结构 ``` src/ ├── simapi_serialization.cj // 根锚点:public import common.* + json.* ├── common/ // XML/JSON 共用 │ ├── annotations.cj // @PropertyName / @Ignore / @NotRequired │ ├── reflection_cache.cj // 字段元数据 + 继承链缓存 │ └── naming_policy.cj // PropertyNamingPolicy ├── json/ // JSON 序列化 │ ├── json_serializer.cj // 公开 API(含 TypeInfo 版 deserialize) │ ├── json_writer.cj / json_reader.cj │ └── json_option.cj ├── macros/ // @SerializerParent 宏 │ └── serializer_parent_macro.cj └── tests/ // 单元测试(独立子包) └── json_serializer_test.cj // cjpm test,21 用例 ``` ## 测试 ```bash cjpm test # TOTAL: 21, PASSED: 21 ``` 覆盖:基础类型、对象字段输出、注解、Option、集合、HashMap、枚举、命名策略、 父类字段(宏)、多层继承、三级继承、maxDepth、宽松类型转换。 ## 设计参考 - API 对齐 .NET `System.Text.Json`(`JsonSerializer.serialize/deserialize` + `JsonSerializerOptions`) - 注解对齐 `[JsonPropertyName]` / `[JsonIgnore]`;默认必填语义为库自身约定,`@NotRequired` 允许字段缺失 - 父类方案参考 soulsoft_serialization 的「宏生成静态类型字段代码」思路 (详见 `docs/父类字段序列化方案.md`)