Files
serialization-cj/README.md
T

261 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<T>(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<String> = []
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<User>(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<T>(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<String, Any>
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<MyDto>()
let obj: Any = JsonSerializer.deserialize(typeInfo, jsonString)
```
### 命名策略(PropertyNamingPolicy)
| 策略 | 效果 |
|---|---|
| `Keep` | 字段名原样(`_username` → `_username`) |
| `CamelCase` | 首字母小写(`Name` → `name`;下划线字段保持原样) |
| `SnakeCase` | 大写转下划线(`userName` → `user_name`) |
> 注:不剥除字段名前导下划线——`_name` 原样输出 `_name`。
## 支持的类型
| 类别 | 说明 |
|---|---|
| 基础类型 | `String` / `Bool` / 全部整数 / 浮点 / `Rune` |
| 集合 | `Array<T>` / `ArrayList<T>` / `HashSet<T>` |
| 字典 | `HashMap<K, V>`(键须可转字符串) |
| `Option<T>` | `Some(x)` → 值;`None` → `null` |
| 枚举 | 构造器名(`Role.Admin` → `"Admin"`) |
| 普通类 | 无参构造 + `var` 字段;嵌套对象递归 |
### 宽松类型转换(反序列化)
JSON 数字 ↔ 字符串字段自动互转:`deserialize<String>("42")` → `"42"`,
`deserialize<Int64>("\"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`)