feat: 注解重命名(@PropertyName/@Ignore) + 新增 @NotRequired 默认必填语义, API 小写化 serialize/deserialize, 宏支持多注解组合, 版本号 1.2.0
This commit is contained in:
@@ -3,9 +3,10 @@
|
|||||||
SimApi 全反射 JSON 序列化库(仓颉版),对齐 .NET `System.Text.Json` 的用法与注解风格。
|
SimApi 全反射 JSON 序列化库(仓颉版),对齐 .NET `System.Text.Json` 的用法与注解风格。
|
||||||
|
|
||||||
- **免标注**:任意类(无参构造 + `var` 字段)无需实现接口、无需标注,开箱即用
|
- **免标注**:任意类(无参构造 + `var` 字段)无需实现接口、无需标注,开箱即用
|
||||||
- **注解**:`@SerializerPropertyName` / `@SerializerIgnore`(对齐 `[JsonPropertyName]` / `[JsonIgnore]`)
|
- **注解**:`@PropertyName` / `@Ignore` / `@NotRequired`(对齐 `[JsonPropertyName]` / `[JsonIgnore]`,默认必填、`@NotRequired` 允许缺失)
|
||||||
|
- **默认必填**:反序列化时要求每个 `var` 字段的键必须存在;标注 `@NotRequired` 的字段允许缺失(保持默认值)
|
||||||
- **继承支持**:`@SerializerParent` 宏解决父类字段序列化(Cangjie 反射限制)
|
- **继承支持**:`@SerializerParent` 宏解决父类字段序列化(Cangjie 反射限制)
|
||||||
- **API 对齐**:`JsonSerializer.Serialize(obj, JsonOption)` / `Deserialize<T>(json, JsonOption)`
|
- **API 对齐**:`JsonSerializer.serialize(obj, JsonOption)` / `deserialize<T>(json, JsonOption)`
|
||||||
- **字段名**:`_` 前缀与无下划线字段均支持(`_name` 原样输出 `_name`,`name` 输出 `name`)
|
- **字段名**:`_` 前缀与无下划线字段均支持(`_name` 原样输出 `_name`,`name` 输出 `name`)
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
@@ -18,14 +19,14 @@ SimApi 全反射 JSON 序列化库(仓颉版),对齐 .NET `System.Text.Jso
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
"simcu::serialization" = "1.0.3"
|
"simcu::serialization" = "1.2.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
**方式二:Git 仓库**
|
**方式二:Git 仓库**
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
"simcu::serialization" = { git = "https://gitcode.com/simcu/serialization-cj.git", version = "1.0.3" }
|
"simcu::serialization" = { git = "https://gitcode.com/simcu/serialization-cj.git", version = "1.2.0" }
|
||||||
```
|
```
|
||||||
|
|
||||||
> 本地开发也可用 path 依赖:`"simcu::serialization" = { path = "../simapi-serialization" }`
|
> 本地开发也可用 path 依赖:`"simcu::serialization" = { path = "../simapi-serialization" }`
|
||||||
@@ -51,10 +52,10 @@ let u = User()
|
|||||||
u._name = "alice"
|
u._name = "alice"
|
||||||
u._age = 30
|
u._age = 30
|
||||||
|
|
||||||
let json = JsonSerializer.Serialize(u)
|
let json = JsonSerializer.serialize(u)
|
||||||
// { "_name": "alice", "_age": 30, "active": true, "_role": "Member", "opt": null, "_tags": [], "addr": {...} }
|
// { "_name": "alice", "_age": 30, "active": true, "_role": "Member", "opt": null, "_tags": [], "addr": {...} }
|
||||||
|
|
||||||
let u2 = JsonSerializer.Deserialize<User>(json)
|
let u2 = JsonSerializer.deserialize<User>(json)
|
||||||
// 全部字段回填
|
// 全部字段回填
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -64,14 +65,52 @@ let u2 = JsonSerializer.Deserialize<User>(json)
|
|||||||
|
|
||||||
```cangjie
|
```cangjie
|
||||||
class User {
|
class User {
|
||||||
@SerializerPropertyName["user_name"]
|
@PropertyName["user_name"]
|
||||||
public var _username: String = "" // JSON 键用 user_name(对齐 [JsonPropertyName])
|
public var _username: String = "" // JSON 键用 user_name(对齐 [JsonPropertyName])
|
||||||
|
|
||||||
@SerializerIgnore
|
@Ignore
|
||||||
public var _temp: String = "" // 序列化/反序列化忽略(对齐 [JsonIgnore])
|
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)
|
## 父类字段序列化(@SerializerParent)
|
||||||
|
|
||||||
### 背景
|
### 背景
|
||||||
@@ -100,7 +139,7 @@ let u = User()
|
|||||||
u._id = "u-1"
|
u._id = "u-1"
|
||||||
u._name = "alice"
|
u._name = "alice"
|
||||||
|
|
||||||
let json = JsonSerializer.Serialize(u)
|
let json = JsonSerializer.serialize(u)
|
||||||
// 输出包含父类字段:{ "_name": "alice", "_id": "u-1", "_createdAt": "" }
|
// 输出包含父类字段:{ "_name": "alice", "_id": "u-1", "_createdAt": "" }
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -112,7 +151,7 @@ let json = JsonSerializer.Serialize(u)
|
|||||||
|---|---|
|
|---|---|
|
||||||
| 自动补 `open` | 非 open 类标注宏后自动可继承 |
|
| 自动补 `open` | 非 open 类标注宏后自动可继承 |
|
||||||
| 多层继承 | 沿 `superClass` 链逐层调用;中间层无宏自动跳过 |
|
| 多层继承 | 沿 `superClass` 链逐层调用;中间层无宏自动跳过 |
|
||||||
| 注解生效 | 父类字段的 `@SerializerPropertyName` / `@SerializerIgnore` 同样生效 |
|
| 注解生效 | 父类字段的 `@PropertyName` / `@Ignore` / `@NotRequired` 同样生效 |
|
||||||
| 任意字段名 | `_` 前缀与无下划线字段都处理 |
|
| 任意字段名 | `_` 前缀与无下划线字段都处理 |
|
||||||
| 向后兼容 | 无宏的类行为不变 |
|
| 向后兼容 | 无宏的类行为不变 |
|
||||||
|
|
||||||
@@ -137,7 +176,7 @@ let opt = JsonOption()
|
|||||||
opt.propertyNamingPolicy = PropertyNamingPolicy.SnakeCase // userName → user_name
|
opt.propertyNamingPolicy = PropertyNamingPolicy.SnakeCase // userName → user_name
|
||||||
opt.maxDepth = 16
|
opt.maxDepth = 16
|
||||||
|
|
||||||
let json = JsonSerializer.Serialize(u, opt)
|
let json = JsonSerializer.serialize(u, opt)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 运行时类型反序列化(框架绑定用)
|
### 运行时类型反序列化(框架绑定用)
|
||||||
@@ -148,7 +187,7 @@ let json = JsonSerializer.Serialize(u, opt)
|
|||||||
import std.reflect.*
|
import std.reflect.*
|
||||||
|
|
||||||
let typeInfo = TypeInfo.of<MyDto>()
|
let typeInfo = TypeInfo.of<MyDto>()
|
||||||
let obj: Any = JsonSerializer.Deserialize(typeInfo, jsonString)
|
let obj: Any = JsonSerializer.deserialize(typeInfo, jsonString)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 命名策略(PropertyNamingPolicy)
|
### 命名策略(PropertyNamingPolicy)
|
||||||
@@ -174,8 +213,8 @@ let obj: Any = JsonSerializer.Deserialize(typeInfo, jsonString)
|
|||||||
|
|
||||||
### 宽松类型转换(反序列化)
|
### 宽松类型转换(反序列化)
|
||||||
|
|
||||||
JSON 数字 ↔ 字符串字段自动互转:`Deserialize<String>("42")` → `"42"`,
|
JSON 数字 ↔ 字符串字段自动互转:`deserialize<String>("42")` → `"42"`,
|
||||||
`Deserialize<Int64>("\"42\"")` → `42`。
|
`deserialize<Int64>("\"42\"")` → `42`。
|
||||||
|
|
||||||
## 已知限制
|
## 已知限制
|
||||||
|
|
||||||
@@ -190,24 +229,24 @@ JSON 数字 ↔ 字符串字段自动互转:`Deserialize<String>("42")` → `"
|
|||||||
src/
|
src/
|
||||||
├── SimApiSerialization.cj // 根锚点:public import common.* + json.*
|
├── SimApiSerialization.cj // 根锚点:public import common.* + json.*
|
||||||
├── common/ // XML/JSON 共用
|
├── common/ // XML/JSON 共用
|
||||||
│ ├── Annotations.cj // @SerializerPropertyName / @SerializerIgnore
|
│ ├── Annotations.cj // @PropertyName / @Ignore / @NotRequired
|
||||||
│ ├── ReflectionCache.cj // 字段元数据 + 继承链缓存
|
│ ├── ReflectionCache.cj // 字段元数据 + 继承链缓存
|
||||||
│ └── NamingPolicy.cj // PropertyNamingPolicy
|
│ └── NamingPolicy.cj // PropertyNamingPolicy
|
||||||
├── json/ // JSON 序列化
|
├── json/ // JSON 序列化
|
||||||
│ ├── JsonSerializer.cj // 公开 API(含 TypeInfo 版 Deserialize)
|
│ ├── JsonSerializer.cj // 公开 API(含 TypeInfo 版 deserialize)
|
||||||
│ ├── JsonWriter.cj / JsonReader.cj
|
│ ├── JsonWriter.cj / JsonReader.cj
|
||||||
│ └── JsonOption.cj
|
│ └── JsonOption.cj
|
||||||
├── macros/ // @SerializerParent 宏
|
├── macros/ // @SerializerParent 宏
|
||||||
│ └── SerializerParentMacro.cj
|
│ └── SerializerParentMacro.cj
|
||||||
└── tests/ // 单元测试(独立子包)
|
└── tests/ // 单元测试(独立子包)
|
||||||
└── JsonSerializer_test.cj // cjpm test,18 用例
|
└── JsonSerializer_test.cj // cjpm test,21 用例
|
||||||
```
|
```
|
||||||
|
|
||||||
## 测试
|
## 测试
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cjpm test
|
cjpm test
|
||||||
# TOTAL: 18, PASSED: 18
|
# TOTAL: 21, PASSED: 21
|
||||||
```
|
```
|
||||||
|
|
||||||
覆盖:基础类型、对象字段输出、注解、Option、集合、HashMap、枚举、命名策略、
|
覆盖:基础类型、对象字段输出、注解、Option、集合、HashMap、枚举、命名策略、
|
||||||
@@ -215,7 +254,7 @@ cjpm test
|
|||||||
|
|
||||||
## 设计参考
|
## 设计参考
|
||||||
|
|
||||||
- API 对齐 .NET `System.Text.Json`(`JsonSerializer.Serialize/Deserialize` + `JsonSerializerOptions`)
|
- API 对齐 .NET `System.Text.Json`(`JsonSerializer.serialize/deserialize` + `JsonSerializerOptions`)
|
||||||
- 注解对齐 `[JsonPropertyName]` / `[JsonIgnore]`
|
- 注解对齐 `[JsonPropertyName]` / `[JsonIgnore]`;默认必填语义为库自身约定,`@NotRequired` 允许字段缺失
|
||||||
- 父类方案参考 soulsoft_serialization 的「宏生成静态类型字段代码」思路
|
- 父类方案参考 soulsoft_serialization 的「宏生成静态类型字段代码」思路
|
||||||
(详见 `docs/父类字段序列化方案.md`)
|
(详见 `docs/父类字段序列化方案.md`)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ cjc-version = "1.1.3"
|
|||||||
name = "serialization"
|
name = "serialization"
|
||||||
organization = "simcu"
|
organization = "simcu"
|
||||||
description = "SimApi 全反射 JSON 序列化库"
|
description = "SimApi 全反射 JSON 序列化库"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
target-dir = ""
|
target-dir = ""
|
||||||
output-type = "static"
|
output-type = "static"
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -183,7 +183,7 @@ import std.collection.*
|
|||||||
public macro SerializerParent(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 字段(跳过 @SerializerIgnore)
|
// 3. 收集 public var 字段(跳过 @Ignore)
|
||||||
// 4. 生成 exportJsonFields / importJsonFields 两个静态方法
|
// 4. 生成 exportJsonFields / importJsonFields 两个静态方法
|
||||||
// 5. 拼回原类声明 + 新方法,返回 Tokens
|
// 5. 拼回原类声明 + 新方法,返回 Tokens
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ if (!isOpenClass(decl)) {
|
|||||||
open class BaseUser {
|
open class BaseUser {
|
||||||
public var _id: String = ""
|
public var _id: String = ""
|
||||||
public var _createdAt: String = ""
|
public var _createdAt: String = ""
|
||||||
@SerializerIgnore
|
@Ignore
|
||||||
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 = ""
|
||||||
@SerializerIgnore
|
@Ignore
|
||||||
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 被 @SerializerIgnore 跳过
|
// _temp 被 @Ignore 跳过
|
||||||
m
|
m
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,8 +318,8 @@ Cangjie 宏需要两步编译(`cjc --compile-macro` 先编宏,再编使用
|
|||||||
| 细节 | 处理 |
|
| 细节 | 处理 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 字段类型 | 用 `TypeInfo.get(字段类型名)` 在运行时做类型转换(import 侧) |
|
| 字段类型 | 用 `TypeInfo.get(字段类型名)` 在运行时做类型转换(import 侧) |
|
||||||
| `@SerializerPropertyName` | export 键用注解名;import 匹配也用注解名 |
|
| `@PropertyName` | export 键用注解名;import 匹配也用注解名 |
|
||||||
| `@SerializerIgnore` | 直接跳过,不生成导出/导入语句 |
|
| `@Ignore` | 直接跳过,不生成导出/导入语句 |
|
||||||
| `let`(不可变)字段 | 只导出,不导入(导入侧跳过) |
|
| `let`(不可变)字段 | 只导出,不导入(导入侧跳过) |
|
||||||
| 无字段 | 生成空方法(返回空 map / 空操作),保证约定方法一定存在 |
|
| 无字段 | 生成空方法(返回空 map / 空操作),保证约定方法一定存在 |
|
||||||
| 命名冲突 | 若用户已手写同名静态方法,宏应报错或跳过(设计取舍:报错更安全) |
|
| 命名冲突 | 若用户已手写同名静态方法,宏应报错或跳过(设计取舍:报错更安全) |
|
||||||
@@ -474,7 +474,7 @@ main() {
|
|||||||
|
|
||||||
| 用例 | 断言 |
|
| 用例 | 断言 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 父类字段序列化 | 输出包含 `_id`/`_createdAt`,且 `@SerializerIgnore` 父类字段不输出 |
|
| 父类字段序列化 | 输出包含 `_id`/`_createdAt`,且 `@Ignore` 父类字段不输出 |
|
||||||
| 父类字段反序列化 | `Deserialize<User>` 后父类字段值正确 |
|
| 父类字段反序列化 | `Deserialize<User>` 后父类字段值正确 |
|
||||||
| 多层继承 | GrandParent → Parent → Child 三层字段全部输出 |
|
| 多层继承 | GrandParent → Parent → Child 三层字段全部输出 |
|
||||||
| 中间层无宏 | 三层链中 Parent 不标宏:`_gp` 仍导出,`_p` 不导出(逐层跳过) |
|
| 中间层无宏 | 三层链中 Parent 不标宏:`_gp` 仍导出,`_p` 不导出(逐层跳过) |
|
||||||
@@ -482,7 +482,7 @@ main() {
|
|||||||
| 子类字段与父类字段重名 | 子类优先(输出一份,值取子类的) |
|
| 子类字段与父类字段重名 | 子类优先(输出一份,值取子类的) |
|
||||||
| 父类嵌套对象字段 | 父类字段值是对象时正确递归 |
|
| 父类嵌套对象字段 | 父类字段值是对象时正确递归 |
|
||||||
| 父类字段 null | 导入时保持默认值 |
|
| 父类字段 null | 导入时保持默认值 |
|
||||||
| 与 @SerializerPropertyName 组合 | 父类字段用注解名输出/匹配 |
|
| 与 @PropertyName 组合 | 父类字段用注解名输出/匹配 |
|
||||||
| 顶层序列化父类类型实例 | `Serialize(BaseUser 实例)` 直接可用 |
|
| 顶层序列化父类类型实例 | `Serialize(BaseUser 实例)` 直接可用 |
|
||||||
| **宏自动补 open** | `@SerializerParent class Base`(无 open)展开后子类可继承(编译通过) |
|
| **宏自动补 open** | `@SerializerParent class Base`(无 open)展开后子类可继承(编译通过) |
|
||||||
| **宏严格模式报错** | `@SerializerParent[requireOpen: true]` 标注非 open class 编译失败 |
|
| **宏严格模式报错** | `@SerializerParent[requireOpen: true]` 标注非 open class 编译失败 |
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* simapi_serialization 根包锚点:聚合重导出,`import simcu::serialization.*`
|
* simapi_serialization 根包锚点:聚合重导出,`import simcu::serialization.*`
|
||||||
* 即可使用全部能力:
|
* 即可使用全部能力:
|
||||||
* - 注解(@SerializerPropertyName / @SerializerIgnore) → common 子包
|
* - 注解(@PropertyName / @Ignore / @NotRequired) → common 子包
|
||||||
* - 序列化器(JsonSerializer / JsonOption) → json 子包
|
* - 序列化器(JsonSerializer / JsonOption) → json 子包
|
||||||
*
|
*
|
||||||
* 子包结构:
|
* 子包结构:
|
||||||
@@ -14,5 +14,13 @@
|
|||||||
|
|
||||||
package simcu::serialization
|
package simcu::serialization
|
||||||
|
|
||||||
public import simcu::serialization.common.*
|
public import simcu::serialization.common.PropertyName
|
||||||
public import simcu::serialization.json.*
|
public import simcu::serialization.common.Ignore
|
||||||
|
public import simcu::serialization.common.NotRequired
|
||||||
|
public import simcu::serialization.common.FieldMetadata
|
||||||
|
public import simcu::serialization.common.ReflectionCache
|
||||||
|
public import simcu::serialization.common.PropertyNamingPolicy
|
||||||
|
public import simcu::serialization.json.JsonSerializer
|
||||||
|
public import simcu::serialization.json.JsonOption
|
||||||
|
public import simcu::serialization.json.JsonReader
|
||||||
|
public import simcu::serialization.json.JsonWriter
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* 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")] → @SerializerPropertyName["xxx"] 指定字段的序列化名称
|
* - [JsonPropertyName("xxx")] → @PropertyName["xxx"] 指定字段的序列化名称
|
||||||
* - [JsonIgnore] → @SerializerIgnore 序列化/反序列化时忽略该字段
|
* - [JsonIgnore] → @Ignore 序列化/反序列化时忽略该字段
|
||||||
|
* - 默认必填语义 → 反序列化时要求每个字段的键必须存在;
|
||||||
|
* `@NotRequired` → 标注的字段允许缺失(键不存在不报错,保持默认值)
|
||||||
*
|
*
|
||||||
* 设计决策:本库序列化/反序列化只处理「var 成员变量」(std.reflect 的 instanceVariables),
|
* 设计决策:本库序列化/反序列化只处理「var 成员变量」(std.reflect 的 instanceVariables),
|
||||||
* 不处理 prop 属性(instanceProperties)。因此注解 target 仅限 MemberVariable:
|
* 不处理 prop 属性(instanceProperties)。因此注解 target 仅限 MemberVariable:
|
||||||
@@ -13,10 +15,10 @@ package simcu::serialization.common
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 指定字段的序列化名称(对齐 .NET JsonPropertyNameAttribute)。
|
* 指定字段的序列化名称(对齐 .NET JsonPropertyNameAttribute)。
|
||||||
* 用法:@SerializerPropertyName["user_name"] public var _username: String = ""
|
* 用法:@PropertyName["user_name"] public var _username: String = ""
|
||||||
*/
|
*/
|
||||||
@Annotation[target: [MemberVariable]]
|
@Annotation[target: [MemberVariable]]
|
||||||
public class SerializerPropertyName {
|
public class PropertyName {
|
||||||
public let name: String
|
public let name: String
|
||||||
|
|
||||||
public const init(name: String) {
|
public const init(name: String) {
|
||||||
@@ -26,9 +28,22 @@ public class SerializerPropertyName {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 标记字段不做序列化处理(对齐 .NET JsonIgnoreAttribute)。
|
* 标记字段不做序列化处理(对齐 .NET JsonIgnoreAttribute)。
|
||||||
* 用法:@SerializerIgnore public var _temp: String = ""
|
* 用法:@Ignore public var _temp: String = ""
|
||||||
*/
|
*/
|
||||||
@Annotation[target: [MemberVariable]]
|
@Annotation[target: [MemberVariable]]
|
||||||
public class SerializerIgnore {
|
public class Ignore {
|
||||||
|
public const init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记字段在反序列化时「允许缺失」(默认语义是全部字段必填)。
|
||||||
|
* 语义:本库默认要求反序列化的 JSON 对象中必须包含每个 var 字段的键;
|
||||||
|
* 标注 @NotRequired 后,该字段的键可以缺失(缺失时保持字段默认值,不抛异常)。
|
||||||
|
* 若键存在但值为 null,仍视为「存在」(保持默认值,不抛异常)。
|
||||||
|
* 仅对反序列化生效,序列化侧不受影响。
|
||||||
|
* 用法:@NotRequired public var _name: String = ""
|
||||||
|
*/
|
||||||
|
@Annotation[target: [MemberVariable]]
|
||||||
|
public class NotRequired {
|
||||||
public const init() {}
|
public const init() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||||
* ReflectionCache:字段元数据(含继承链、命名策略、@SerializerPropertyName/@SerializerIgnore)与缓存。
|
* ReflectionCache:字段元数据(含继承链、命名策略、@PropertyName/@Ignore/@NotRequired)与缓存。
|
||||||
*
|
*
|
||||||
* 已知限制(平台级):
|
* 已知限制(平台级):
|
||||||
* - InstanceVariableInfo.getValue/setValue 有「声明类严格类型校验」,父类字段无法用子类实例读写,
|
* - InstanceVariableInfo.getValue/setValue 有「声明类严格类型校验」,父类字段无法用子类实例读写,
|
||||||
@@ -9,8 +9,11 @@
|
|||||||
|
|
||||||
package simcu::serialization.common
|
package simcu::serialization.common
|
||||||
|
|
||||||
import std.collection.*
|
import std.collection.ArrayList
|
||||||
import std.reflect.*
|
import std.collection.HashMap
|
||||||
|
import std.reflect.ClassTypeInfo
|
||||||
|
import std.reflect.InstanceVariableInfo
|
||||||
|
import std.reflect.TypeInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单个字段的序列化元数据。
|
* 单个字段的序列化元数据。
|
||||||
@@ -18,10 +21,12 @@ import std.reflect.*
|
|||||||
public class FieldMetadata {
|
public class FieldMetadata {
|
||||||
/// 反射字段名(如 _name)
|
/// 反射字段名(如 _name)
|
||||||
public var name: String = ""
|
public var name: String = ""
|
||||||
/// JSON 名称(@SerializerPropertyName 优先,否则按命名策略)
|
/// JSON 名称(@PropertyName 优先,否则按命名策略)
|
||||||
public var jsonName: String = ""
|
public var jsonName: String = ""
|
||||||
/// 是否忽略(@SerializerIgnore)
|
/// 是否忽略(@Ignore)
|
||||||
public var ignore: Bool = false
|
public var ignore: Bool = false
|
||||||
|
/// 是否必填(默认 true:反序列化时要求该字段必须存在;@NotRequired 则允许缺失)
|
||||||
|
public var required: Bool = true
|
||||||
/// 字段类型
|
/// 字段类型
|
||||||
public var typeInfo: ?TypeInfo = None
|
public var typeInfo: ?TypeInfo = None
|
||||||
/// 字段读写句柄
|
/// 字段读写句柄
|
||||||
@@ -36,25 +41,25 @@ public class FieldMetadata {
|
|||||||
public class ReflectionCache {
|
public class ReflectionCache {
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
private static let _cache = HashMap<String, ArrayList<FieldMetadata>>()
|
private static let _CACHE = HashMap<String, ArrayList<FieldMetadata>>()
|
||||||
|
|
||||||
/// 类型 → 是否有 @SerializerParent 宏生成的 exportJsonFields(Bool)
|
/// 类型 → 是否有 @SerializerParent 宏生成的 exportJsonFields(Bool)
|
||||||
private static let _exportCache = HashMap<String, Bool>()
|
private static let _EXPORT_CACHE = HashMap<String, Bool>()
|
||||||
/// 类型 → 是否有 @SerializerParent 宏生成的 importJsonFields(Bool)
|
/// 类型 → 是否有 @SerializerParent 宏生成的 importJsonFields(Bool)
|
||||||
private static let _importCache = HashMap<String, Bool>()
|
private static let _IMPORT_CACHE = HashMap<String, Bool>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 该类型是否含 @SerializerParent 宏生成的导出静态方法(含缓存)。
|
* 该类型是否含 @SerializerParent 宏生成的导出静态方法(含缓存)。
|
||||||
*/
|
*/
|
||||||
public static func hasExportFields(typeInfo: ClassTypeInfo): Bool {
|
public static func hasExportFields(typeInfo: ClassTypeInfo): Bool {
|
||||||
hasParentMethod(typeInfo, "exportJsonFields", _exportCache)
|
hasParentMethod(typeInfo, "exportJsonFields", _EXPORT_CACHE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 该类型是否含 @SerializerParent 宏生成的导入静态方法(含缓存)。
|
* 该类型是否含 @SerializerParent 宏生成的导入静态方法(含缓存)。
|
||||||
*/
|
*/
|
||||||
public static func hasImportFields(typeInfo: ClassTypeInfo): Bool {
|
public static func hasImportFields(typeInfo: ClassTypeInfo): Bool {
|
||||||
hasParentMethod(typeInfo, "importJsonFields", _importCache)
|
hasParentMethod(typeInfo, "importJsonFields", _IMPORT_CACHE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func hasParentMethod(typeInfo: ClassTypeInfo, name: String, cache: HashMap<String, Bool>): Bool {
|
private static func hasParentMethod(typeInfo: ClassTypeInfo, name: String, cache: HashMap<String, Bool>): Bool {
|
||||||
@@ -84,7 +89,7 @@ public class ReflectionCache {
|
|||||||
*/
|
*/
|
||||||
public static func getFields(typeInfo: ClassTypeInfo, namingPolicy: PropertyNamingPolicy): ArrayList<FieldMetadata> {
|
public static func getFields(typeInfo: ClassTypeInfo, namingPolicy: PropertyNamingPolicy): ArrayList<FieldMetadata> {
|
||||||
let key = typeInfo.qualifiedName
|
let key = typeInfo.qualifiedName
|
||||||
if (let Some(cached) <- _cache.get(key)) {
|
if (let Some(cached) <- _CACHE.get(key)) {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
var fields = ArrayList<FieldMetadata>()
|
var fields = ArrayList<FieldMetadata>()
|
||||||
@@ -94,13 +99,17 @@ 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()
|
||||||
// @SerializerIgnore
|
// @Ignore
|
||||||
if (v.findAnnotation<SerializerIgnore>().isSome()) {
|
if (v.findAnnotation<Ignore>().isSome()) {
|
||||||
meta.ignore = true
|
meta.ignore = true
|
||||||
}
|
}
|
||||||
// @SerializerPropertyName 优先
|
// @NotRequired:反序列化时允许该字段缺失(默认所有字段必填)
|
||||||
|
if (v.findAnnotation<NotRequired>().isSome()) {
|
||||||
|
meta.required = false
|
||||||
|
}
|
||||||
|
// @PropertyName 优先
|
||||||
var jsonName = ""
|
var jsonName = ""
|
||||||
if (let Some(jsonProp) <- v.findAnnotation<SerializerPropertyName>()) {
|
if (let Some(jsonProp) <- v.findAnnotation<PropertyName>()) {
|
||||||
jsonName = jsonProp.name
|
jsonName = jsonProp.name
|
||||||
} else {
|
} else {
|
||||||
jsonName = applyNamingPolicy(v.name, namingPolicy)
|
jsonName = applyNamingPolicy(v.name, namingPolicy)
|
||||||
@@ -108,7 +117,7 @@ public class ReflectionCache {
|
|||||||
meta.jsonName = jsonName
|
meta.jsonName = jsonName
|
||||||
fields.add(meta)
|
fields.add(meta)
|
||||||
}
|
}
|
||||||
_cache[key] = fields
|
_CACHE[key] = fields
|
||||||
fields
|
fields
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,19 +147,18 @@ public class ReflectionCache {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
var sb = StringBuilder()
|
var sb = StringBuilder()
|
||||||
var first = true
|
// 首字符单独处理:大写转小写且不加前置下划线
|
||||||
for (c in s.runes()) {
|
sb.append(s[0..1].toAsciiLower())
|
||||||
|
let rest = s[1..]
|
||||||
|
for (c in rest.runes()) {
|
||||||
let ch = UInt32(c)
|
let ch = UInt32(c)
|
||||||
let isUpper = ch >= 0x41 && ch <= 0x5A
|
let isUpper = ch >= 0x41 && ch <= 0x5A
|
||||||
if (isUpper) {
|
if (isUpper) {
|
||||||
if (!first) {
|
sb.append("_")
|
||||||
sb.append("_")
|
|
||||||
}
|
|
||||||
sb.append(Rune(ch + 0x20))
|
sb.append(Rune(ch + 0x20))
|
||||||
} else {
|
} else {
|
||||||
sb.append(c)
|
sb.append(c)
|
||||||
}
|
}
|
||||||
first = false
|
|
||||||
}
|
}
|
||||||
sb.toString()
|
sb.toString()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||||
* JsonOption:序列化选项(对齐 .NET JsonSerializerOptions)。
|
* JsonOption:序列化选项。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package simcu::serialization.json
|
package simcu::serialization.json
|
||||||
|
|
||||||
import simcu::serialization.common.*
|
import simcu::serialization.common.PropertyNamingPolicy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 序列化选项。
|
* 序列化选项。
|
||||||
@@ -21,5 +21,5 @@ public class JsonOption {
|
|||||||
public var maxDepth: Int64 = 64
|
public var maxDepth: Int64 = 64
|
||||||
|
|
||||||
/// 默认选项实例
|
/// 默认选项实例
|
||||||
public static let instance: JsonOption = JsonOption()
|
public static let INSTANCE: JsonOption = JsonOption()
|
||||||
}
|
}
|
||||||
|
|||||||
+121
-54
@@ -9,11 +9,22 @@
|
|||||||
|
|
||||||
package simcu::serialization.json
|
package simcu::serialization.json
|
||||||
|
|
||||||
import std.collection.*
|
import std.collection.ArrayList
|
||||||
import std.convert.*
|
import std.collection.HashMap
|
||||||
import std.reflect.*
|
import std.convert.Parsable
|
||||||
import std.time.*
|
import std.reflect.ClassTypeInfo
|
||||||
import stdx.encoding.json.*
|
import std.reflect.EnumTypeInfo
|
||||||
|
import std.reflect.InstanceFunctionInfo
|
||||||
|
import std.reflect.TypeInfo
|
||||||
|
import std.time.DateTime
|
||||||
|
import stdx.encoding.json.JsonArray
|
||||||
|
import stdx.encoding.json.JsonBool
|
||||||
|
import stdx.encoding.json.JsonFloat
|
||||||
|
import stdx.encoding.json.JsonInt
|
||||||
|
import stdx.encoding.json.JsonNull
|
||||||
|
import stdx.encoding.json.JsonObject
|
||||||
|
import stdx.encoding.json.JsonString
|
||||||
|
import stdx.encoding.json.JsonValue
|
||||||
import simcu::serialization.common.ReflectionCache
|
import simcu::serialization.common.ReflectionCache
|
||||||
import simcu::serialization.common.FieldMetadata
|
import simcu::serialization.common.FieldMetadata
|
||||||
|
|
||||||
@@ -23,6 +34,13 @@ import simcu::serialization.common.FieldMetadata
|
|||||||
public class JsonReader {
|
public class JsonReader {
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反序列化 JSON 值为目标类型 T。
|
||||||
|
* @param value JSON 值。
|
||||||
|
* @param options 序列化选项。
|
||||||
|
* @return 目标类型实例。
|
||||||
|
* @throws Exception 结果类型不匹配或反序列化失败时。
|
||||||
|
*/
|
||||||
public static func read<T>(value: JsonValue, options: JsonOption): T {
|
public static func read<T>(value: JsonValue, options: JsonOption): T {
|
||||||
let result = readValue(value, TypeInfo.of<T>(), options, 0)
|
let result = readValue(value, TypeInfo.of<T>(), options, 0)
|
||||||
if (let t: T <- result) {
|
if (let t: T <- result) {
|
||||||
@@ -37,11 +55,16 @@ public class JsonReader {
|
|||||||
* @param value JSON 值。
|
* @param value JSON 值。
|
||||||
* @param options 序列化选项。
|
* @param options 序列化选项。
|
||||||
* @return 反序列化后的实例(Any)。
|
* @return 反序列化后的实例(Any)。
|
||||||
|
* @throws Exception 反序列化失败时。
|
||||||
*/
|
*/
|
||||||
public static func read(typeInfo: TypeInfo, value: JsonValue, options: JsonOption): Any {
|
public static func read(typeInfo: TypeInfo, value: JsonValue, options: JsonOption): Any {
|
||||||
readValue(value, typeInfo, options, 0)
|
readValue(value, typeInfo, options, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按类型信息递归反序列化。
|
||||||
|
* @throws Exception 深度超限、类型不支持或转换失败时。
|
||||||
|
*/
|
||||||
private static func readValue(value: JsonValue, typeInfo: TypeInfo, options: JsonOption, depth: Int64): Any {
|
private static func readValue(value: JsonValue, typeInfo: TypeInfo, options: JsonOption, depth: Int64): Any {
|
||||||
if (depth > options.maxDepth) {
|
if (depth > options.maxDepth) {
|
||||||
throw Exception("JsonSerializer: 超过最大递归深度 ${options.maxDepth}")
|
throw Exception("JsonSerializer: 超过最大递归深度 ${options.maxDepth}")
|
||||||
@@ -83,11 +106,12 @@ public class JsonReader {
|
|||||||
return noneVal
|
return noneVal
|
||||||
}
|
}
|
||||||
let innerName = JsonWriter.extractTypeArgs(typeName)[0]
|
let innerName = JsonWriter.extractTypeArgs(typeName)[0]
|
||||||
let innerVal = readValue(value, TypeInfo.get(innerName), options, depth + 1)
|
|
||||||
if (let et: EnumTypeInfo <- typeInfo) {
|
if (let et: EnumTypeInfo <- typeInfo) {
|
||||||
|
let innerVal = readValue(value, TypeInfo.get(innerName), options, depth + 1)
|
||||||
let ctor = et.getConstructor("Some", argsCount: 1)
|
let ctor = et.getConstructor("Some", argsCount: 1)
|
||||||
return ctor.apply([innerVal])
|
return ctor.apply([innerVal])
|
||||||
}
|
}
|
||||||
|
let innerVal = readValue(value, TypeInfo.get(innerName), options, depth + 1)
|
||||||
return Some(innerVal)
|
return Some(innerVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +155,6 @@ public class JsonReader {
|
|||||||
private static func readObject(value: JsonValue, ct: ClassTypeInfo, options: JsonOption, depth: Int64): Any {
|
private static func readObject(value: JsonValue, ct: ClassTypeInfo, options: JsonOption, depth: Int64): Any {
|
||||||
let instance = ct.construct([])
|
let instance = ct.construct([])
|
||||||
let fields = ReflectionCache.getFields(ct, options.propertyNamingPolicy)
|
let fields = ReflectionCache.getFields(ct, options.propertyNamingPolicy)
|
||||||
let jobj = value.asObject()
|
|
||||||
for (f in fields) {
|
for (f in fields) {
|
||||||
if (f.ignore) {
|
if (f.ignore) {
|
||||||
continue
|
continue
|
||||||
@@ -139,14 +162,18 @@ public class JsonReader {
|
|||||||
if (!f.mutable) {
|
if (!f.mutable) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
match (jobj.get(f.jsonName)) {
|
match (value.asObject().get(f.jsonName)) {
|
||||||
case Some(jv) =>
|
case Some(jv) =>
|
||||||
if (jv is JsonNull) {
|
if (jv is JsonNull) {
|
||||||
continue // null:跳过,保持字段默认值
|
continue // null:跳过,保持字段默认值
|
||||||
}
|
}
|
||||||
let fieldVal = readValue(jv, f.typeInfo.getOrThrow(), options, depth + 1)
|
let fieldVal = readValue(jv, f.typeInfo.getOrThrow(), options, depth + 1)
|
||||||
f.variable.getOrThrow().setValue(instance, fieldVal)
|
f.variable.getOrThrow().setValue(instance, fieldVal)
|
||||||
case None => ()
|
case None =>
|
||||||
|
// 默认必填:JSON 键必须存在,否则抛异常(@NotRequired 字段已置 required=false)
|
||||||
|
if (f.required) {
|
||||||
|
throw Exception("JsonSerializer: 缺少必填字段 \"${f.jsonName}\"")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 父类字段(@SerializerParent 宏生成的静态导入方法,沿继承链逐层回填)
|
// 父类字段(@SerializerParent 宏生成的静态导入方法,沿继承链逐层回填)
|
||||||
@@ -158,7 +185,6 @@ public class JsonReader {
|
|||||||
/// 无宏的父类(hasImportFields 缓存判断)静默跳过;宏生成的方法内部按
|
/// 无宏的父类(hasImportFields 缓存判断)静默跳过;宏生成的方法内部按
|
||||||
/// if (k == "字段名") 忽略未知键,因此直接把全部 JSON 键传入即可。
|
/// if (k == "字段名") 忽略未知键,因此直接把全部 JSON 键传入即可。
|
||||||
private static func readParentFields(value: JsonValue, ct: ClassTypeInfo, instance: Any): Unit {
|
private static func readParentFields(value: JsonValue, ct: ClassTypeInfo, instance: Any): Unit {
|
||||||
let jobj = value.asObject()
|
|
||||||
var parent = ct.superClass
|
var parent = ct.superClass
|
||||||
while (let Some(p) <- parent) {
|
while (let Some(p) <- parent) {
|
||||||
if (ReflectionCache.hasImportFields(p)) {
|
if (ReflectionCache.hasImportFields(p)) {
|
||||||
@@ -166,95 +192,118 @@ public class JsonReader {
|
|||||||
let fn = p.getStaticFunction("importJsonFields",
|
let fn = p.getStaticFunction("importJsonFields",
|
||||||
[p, TypeInfo.of<HashMap<String, Any>>()])
|
[p, TypeInfo.of<HashMap<String, Any>>()])
|
||||||
var pjson = HashMap<String, Any>()
|
var pjson = HashMap<String, Any>()
|
||||||
for ((k, jv) in jobj.getFields()) {
|
for ((k, jv) in value.asObject().getFields()) {
|
||||||
if (!(jv is JsonNull)) {
|
// 保留所有键(含 null 值),使默认必填的「键必须存在」判定与
|
||||||
pjson[k] = jsonToAny(jv)
|
// 反射路径一致:null 值字段仍视为「存在」,导入时按类型匹配失败跳过
|
||||||
}
|
pjson[k] = jsonToAny(jv)
|
||||||
}
|
}
|
||||||
let args: Array<Any> = [instance, pjson]
|
let args: Array<Any> = [instance, pjson]
|
||||||
fn.apply(p, args)
|
fn.apply(p, args)
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
// 导入失败(如类型不匹配)不中断整体反序列化
|
// 默认必填字段缺失 → 中断整体反序列化
|
||||||
|
if (e.message.contains("缺少必填字段")) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
// 其余导入失败(如类型不匹配)不中断整体反序列化
|
||||||
|
let _ = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parent = p.superClass
|
parent = p.superClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Array<X>:经 ArrayList<X> 构造 + add + toArray
|
/// Array<X>:经 ArrayList<X> 构造 + add + toArray。
|
||||||
|
/// @throws Exception 目标类型解析失败时。
|
||||||
private static func readArray(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any {
|
private static func readArray(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any {
|
||||||
let listTypeName = "std.collection.ArrayList<${elemName}>"
|
let listTypeName = "std.collection.ArrayList<${elemName}>"
|
||||||
let listCt = (TypeInfo.get(listTypeName) as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析类型 ${listTypeName}")
|
let listCt = (TypeInfo.get(listTypeName) as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析类型 ${listTypeName}")
|
||||||
let list = listCt.construct([])
|
let list = listCt.construct([])
|
||||||
let addFunc = listCt.getInstanceFunction("add", [TypeInfo.get(elemName)])
|
let addFunc = listCt.getInstanceFunction("add", [TypeInfo.get(elemName)])
|
||||||
let jarr = value.asArray()
|
let jarr = value.asArray()
|
||||||
for (item in jarr.getItems()) {
|
fillCollection(list, addFunc, jarr, elemName, options, depth)
|
||||||
let elem = readValue(item, TypeInfo.get(elemName), options, depth + 1)
|
|
||||||
addFunc.apply(list, [elem])
|
|
||||||
}
|
|
||||||
let toArrayFunc = listCt.getInstanceFunction("toArray", [])
|
let toArrayFunc = listCt.getInstanceFunction("toArray", [])
|
||||||
toArrayFunc.apply(list, [])
|
toArrayFunc.apply(list, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 反射批量填充单值集合(Array/ArrayList/HashSet)。
|
||||||
|
/// addFunc 为只查询一次的 add 函数句柄,作为参数传入以遵循最小作用域。
|
||||||
|
private static func fillCollection(collection: Any, addFunc: InstanceFunctionInfo, jarr: JsonArray, elemName: String,
|
||||||
|
options: JsonOption, depth: Int64): Unit {
|
||||||
|
for (item in jarr.getItems()) {
|
||||||
|
let elem = readValue(item, TypeInfo.get(elemName), options, depth + 1)
|
||||||
|
addFunc.apply(collection, [elem])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 反序列化 ArrayList<X>。
|
||||||
|
/// @throws Exception 目标类型解析失败时。
|
||||||
private static func readArrayList(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any {
|
private static func readArrayList(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any {
|
||||||
let ct = (TypeInfo.get("std.collection.ArrayList<${elemName}>") as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析 ArrayList<${elemName}>")
|
let ct = (TypeInfo.get("std.collection.ArrayList<${elemName}>") as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析 ArrayList<${elemName}>")
|
||||||
let list = ct.construct([])
|
let list = ct.construct([])
|
||||||
let addFunc = ct.getInstanceFunction("add", [TypeInfo.get(elemName)])
|
let addFunc = ct.getInstanceFunction("add", [TypeInfo.get(elemName)])
|
||||||
let jarr = value.asArray()
|
let jarr = value.asArray()
|
||||||
for (item in jarr.getItems()) {
|
fillCollection(list, addFunc, jarr, elemName, options, depth)
|
||||||
let elem = readValue(item, TypeInfo.get(elemName), options, depth + 1)
|
|
||||||
addFunc.apply(list, [elem])
|
|
||||||
}
|
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 反序列化 HashSet<X>。
|
||||||
|
/// @throws Exception 目标类型解析失败时。
|
||||||
private static func readHashSet(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any {
|
private static func readHashSet(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any {
|
||||||
let ct = (TypeInfo.get("std.collection.HashSet<${elemName}>") as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析 HashSet<${elemName}>")
|
let ct = (TypeInfo.get("std.collection.HashSet<${elemName}>") as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析 HashSet<${elemName}>")
|
||||||
let set = ct.construct([])
|
let set = ct.construct([])
|
||||||
let addFunc = ct.getInstanceFunction("add", [TypeInfo.get(elemName)])
|
let addFunc = ct.getInstanceFunction("add", [TypeInfo.get(elemName)])
|
||||||
let jarr = value.asArray()
|
let jarr = value.asArray()
|
||||||
for (item in jarr.getItems()) {
|
fillCollection(set, addFunc, jarr, elemName, options, depth)
|
||||||
let elem = readValue(item, TypeInfo.get(elemName), options, depth + 1)
|
|
||||||
addFunc.apply(set, [elem])
|
|
||||||
}
|
|
||||||
set
|
set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 反序列化 HashMap<K, V>。
|
||||||
|
/// @throws Exception 目标类型解析失败时。
|
||||||
private static func readHashMap(value: JsonValue, keyName: String, valName: String, options: JsonOption,
|
private static func readHashMap(value: JsonValue, keyName: String, valName: String, options: JsonOption,
|
||||||
depth: Int64): Any {
|
depth: Int64): Any {
|
||||||
let ct = (TypeInfo.get("std.collection.HashMap<${keyName}, ${valName}>") as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析 HashMap<${keyName}, ${valName}>")
|
let ct = (TypeInfo.get("std.collection.HashMap<${keyName}, ${valName}>") as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析 HashMap<${keyName}, ${valName}>")
|
||||||
let map = ct.construct([])
|
let map = ct.construct([])
|
||||||
let addFunc = ct.getInstanceFunction("add", [TypeInfo.get(keyName), TypeInfo.get(valName)])
|
let addFunc = ct.getInstanceFunction("add", [TypeInfo.get(keyName), TypeInfo.get(valName)])
|
||||||
let jobj = value.asObject()
|
let jobj = value.asObject()
|
||||||
|
fillHashMap(map, addFunc, jobj, keyName, valName, options, depth)
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 反射批量填充 HashMap。addFunc 为只查询一次的 add 函数句柄,作为参数传入以遵循最小作用域。
|
||||||
|
private static func fillHashMap(map: Any, addFunc: InstanceFunctionInfo, jobj: JsonObject, keyName: String,
|
||||||
|
valName: String,
|
||||||
|
options: JsonOption, depth: Int64): Unit {
|
||||||
for ((k, jv) in jobj.getFields()) {
|
for ((k, jv) in jobj.getFields()) {
|
||||||
let keyVal = readValue(JsonString(k), TypeInfo.get(keyName), options, depth + 1)
|
let keyVal = readValue(JsonString(k), TypeInfo.get(keyName), options, depth + 1)
|
||||||
let valVal = readValue(jv, TypeInfo.get(valName), options, depth + 1)
|
let valVal = readValue(jv, TypeInfo.get(valName), options, depth + 1)
|
||||||
addFunc.apply(map, [keyVal, valVal])
|
addFunc.apply(map, [keyVal, valVal])
|
||||||
}
|
}
|
||||||
map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JsonValue → 通用 Any(动态结构)
|
/// JsonValue → 通用 Any(动态结构)
|
||||||
private static func jsonToAny(v: JsonValue): Any {
|
private static func jsonToAny(v: JsonValue): Any {
|
||||||
match (v.kind()) {
|
// 注意:不能用 v.kind() 做分派——cjc 1.1.3 会把对虚方法 kind() 的调用
|
||||||
case JsNull => noneAny()
|
// 编译期折叠为 JsNull,导致所有值被误判为 null。改用 is 类型判断。
|
||||||
case JsBool => v.asBool().getValue()
|
if (let s: JsonString <- v) { return s.getValue() }
|
||||||
case JsInt => v.asInt().getValue()
|
if (let b: JsonBool <- v) { return b.getValue() }
|
||||||
case JsFloat => v.asFloat().getValue()
|
if (let i: JsonInt <- v) { return i.getValue() }
|
||||||
case JsString => v.asString().getValue()
|
if (let f: JsonFloat <- v) { return f.getValue() }
|
||||||
case JsArray =>
|
if (v is JsonNull) { return noneAny() }
|
||||||
var list = ArrayList<Any>()
|
if (let a: JsonArray <- v) {
|
||||||
for (item in v.asArray().getItems()) {
|
var list = ArrayList<Any>()
|
||||||
list.add(jsonToAny(item))
|
for (item in a.getItems()) {
|
||||||
}
|
list.add(jsonToAny(item))
|
||||||
list.toArray()
|
}
|
||||||
case JsObject =>
|
return list.toArray()
|
||||||
var map = HashMap<String, Any>()
|
|
||||||
for ((k, val) in v.asObject().getFields()) {
|
|
||||||
map[k] = jsonToAny(val)
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
}
|
||||||
|
if (let o: JsonObject <- v) {
|
||||||
|
var map = HashMap<String, Any>()
|
||||||
|
for ((k, val) in o.getFields()) {
|
||||||
|
map[k] = jsonToAny(val)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
noneAny()
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func noneAny(): Any {
|
private static func noneAny(): Any {
|
||||||
@@ -264,6 +313,8 @@ public class JsonReader {
|
|||||||
|
|
||||||
// ===== 宽松类型转换 =====
|
// ===== 宽松类型转换 =====
|
||||||
|
|
||||||
|
/// 宽松转换为 String。
|
||||||
|
/// @throws Exception 无法转换时。
|
||||||
private static func readString(v: JsonValue): String {
|
private static func readString(v: JsonValue): String {
|
||||||
if (let s: JsonString <- v) { return s.getValue() }
|
if (let s: JsonString <- v) { return s.getValue() }
|
||||||
if (let i: JsonInt <- v) { return "${i.getValue()}" }
|
if (let i: JsonInt <- v) { return "${i.getValue()}" }
|
||||||
@@ -272,6 +323,8 @@ public class JsonReader {
|
|||||||
throw Exception("JsonSerializer: 无法转换为 String")
|
throw Exception("JsonSerializer: 无法转换为 String")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 宽松转换为 Int64。
|
||||||
|
/// @throws Exception 无法转换时。
|
||||||
private static func readInt64(v: JsonValue): Int64 {
|
private static func readInt64(v: JsonValue): Int64 {
|
||||||
if (let i: JsonInt <- v) { return i.getValue() }
|
if (let i: JsonInt <- v) { return i.getValue() }
|
||||||
if (let f: JsonFloat <- v) { return Int64(f.getValue()) }
|
if (let f: JsonFloat <- v) { return Int64(f.getValue()) }
|
||||||
@@ -279,6 +332,8 @@ public class JsonReader {
|
|||||||
throw Exception("JsonSerializer: 无法转换为 Int64")
|
throw Exception("JsonSerializer: 无法转换为 Int64")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 宽松转换为 Float64。
|
||||||
|
/// @throws Exception 无法转换时。
|
||||||
private static func readFloat64(v: JsonValue): Float64 {
|
private static func readFloat64(v: JsonValue): Float64 {
|
||||||
if (let f: JsonFloat <- v) { return f.getValue() }
|
if (let f: JsonFloat <- v) { return f.getValue() }
|
||||||
if (let i: JsonInt <- v) { return Float64(i.getValue()) }
|
if (let i: JsonInt <- v) { return Float64(i.getValue()) }
|
||||||
@@ -286,22 +341,32 @@ public class JsonReader {
|
|||||||
throw Exception("JsonSerializer: 无法转换为 Float64")
|
throw Exception("JsonSerializer: 无法转换为 Float64")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 宽松转换为 Bool。
|
||||||
|
/// @throws Exception 无法转换时。
|
||||||
private static func readBool(v: JsonValue): Bool {
|
private static func readBool(v: JsonValue): Bool {
|
||||||
if (let b: JsonBool <- v) { return b.getValue() }
|
if (let b: JsonBool <- v) { return b.getValue() }
|
||||||
if (let s: JsonString <- v) { return s.getValue() == "true" }
|
if (let s: JsonString <- v) { return s.getValue() == "true" }
|
||||||
throw Exception("JsonSerializer: 无法转换为 Bool")
|
throw Exception("JsonSerializer: 无法转换为 Bool")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 取首字符作为 Rune。
|
||||||
|
/// @throws Exception 字符串为空无法转换时。
|
||||||
private static func readChar(v: JsonValue): Rune {
|
private static func readChar(v: JsonValue): Rune {
|
||||||
let s = readString(v)
|
for (r in readString(v).runes()) {
|
||||||
for (r in s.runes()) {
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
throw Exception("JsonSerializer: 无法转换为 Char")
|
throw Exception("JsonSerializer: 无法转换为 Char")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 解析 DateTime(格式 yyyy-MM-dd HH:mm:ss)。
|
||||||
|
/// @throws Exception 格式不匹配无法转换时。
|
||||||
private static func readDateTime(v: JsonValue): DateTime {
|
private static func readDateTime(v: JsonValue): DateTime {
|
||||||
let s = readString(v)
|
parseDateTime(readString(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 严格解析 DateTime,失败时抛出带原始字符串的错误信息。
|
||||||
|
/// @throws Exception 格式不匹配无法转换时。
|
||||||
|
private static func parseDateTime(s: String): DateTime {
|
||||||
try {
|
try {
|
||||||
return DateTime.parse(s, "yyyy-MM-dd HH:mm:ss")
|
return DateTime.parse(s, "yyyy-MM-dd HH:mm:ss")
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
@@ -310,6 +375,7 @@ public class JsonReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 生成某类型的占位默认值(用于构造 Option<X> 的 None:None 的参数槽类型是 X)。
|
/// 生成某类型的占位默认值(用于构造 Option<X> 的 None:None 的参数槽类型是 X)。
|
||||||
|
/// @throws Exception 无法构造占位值时。
|
||||||
private static func defaultValueFor(typeName: String): Any {
|
private static func defaultValueFor(typeName: String): Any {
|
||||||
match (typeName) {
|
match (typeName) {
|
||||||
case "String" => return ""
|
case "String" => return ""
|
||||||
@@ -334,9 +400,10 @@ public class JsonReader {
|
|||||||
}
|
}
|
||||||
if (let et: EnumTypeInfo <- TypeInfo.get(typeName)) {
|
if (let et: EnumTypeInfo <- TypeInfo.get(typeName)) {
|
||||||
// constructors 是 Collection:取第一个构造器名
|
// constructors 是 Collection:取第一个构造器名
|
||||||
let first = et.constructors.iterator().next().getOrThrow()
|
if (let Some(first) <- et.constructors.iterator().next()) {
|
||||||
let ctor = et.getConstructor(first.name, argsCount: 0)
|
let ctor = et.getConstructor(first.name, argsCount: 0)
|
||||||
return ctor.apply([])
|
return ctor.apply([])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw Exception("JsonSerializer: 无法为 Option 占位值构造类型 ${typeName}")
|
throw Exception("JsonSerializer: 无法为 Option 占位值构造类型 ${typeName}")
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-21
@@ -1,19 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||||
* JsonSerializer:公开 API,对齐 .NET System.Text.Json:
|
* JsonSerializer:公开 API:
|
||||||
* JsonSerializer.Serialize(obj, JsonOption) → JSON 字符串
|
* serialize(obj, JsonOption) → JSON 字符串
|
||||||
* JsonSerializer.Deserialize<T>(jsonString, JsonOption) → T
|
* deserialize<T>(jsonString, JsonOption) → T
|
||||||
|
* deserialize<T>(typeInfo, jsonString, JsonOption) → T(运行时类型信息绑定)
|
||||||
*
|
*
|
||||||
* 全反射实现:任意类(无参构造 + var 字段)无需实现接口、无需宏。
|
* 全反射实现:任意类(无参构造 + var 字段)无需实现接口、无需宏。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package simcu::serialization.json
|
package simcu::serialization.json
|
||||||
|
|
||||||
import std.reflect.*
|
import std.reflect.TypeInfo
|
||||||
import stdx.encoding.json.*
|
import stdx.encoding.json.JsonValue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全反射 JSON 序列化器(对齐 .NET JsonSerializer)。
|
* 全反射 JSON 序列化器。
|
||||||
*/
|
*/
|
||||||
public class JsonSerializer {
|
public class JsonSerializer {
|
||||||
private init() {}
|
private init() {}
|
||||||
@@ -21,54 +22,71 @@ public class JsonSerializer {
|
|||||||
/**
|
/**
|
||||||
* 序列化任意对象为 JSON 字符串。
|
* 序列化任意对象为 JSON 字符串。
|
||||||
* @param obj 任意对象(基础类型/集合/HashMap/枚举/普通类)。
|
* @param obj 任意对象(基础类型/集合/HashMap/枚举/普通类)。
|
||||||
* @param options 序列化选项(默认 JsonOption.instance)。
|
* @param options 序列化选项(默认 JsonOption.INSTANCE)。
|
||||||
* @return JSON 字符串。
|
* @return JSON 字符串。
|
||||||
|
* @throws Exception 类型不支持或超过递归深度时抛出。
|
||||||
*/
|
*/
|
||||||
public static func Serialize(obj: Any, options: JsonOption): String {
|
public static func serialize<T>(obj: T, options: JsonOption): String {
|
||||||
JsonWriter.write(obj, options).toJsonString()
|
JsonWriter.write(obj, options).toJsonString()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 序列化任意对象为 JSON 字符串(默认选项)。
|
* 序列化任意对象为 JSON 字符串(默认选项)。
|
||||||
|
* @param obj 任意对象。
|
||||||
|
* @return JSON 字符串。
|
||||||
|
* @throws Exception 类型不支持或超过递归深度时抛出。
|
||||||
*/
|
*/
|
||||||
public static func Serialize(obj: Any): String {
|
public static func serialize<T>(obj: T): String {
|
||||||
Serialize(obj, JsonOption.instance)
|
serialize(obj, JsonOption.INSTANCE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 反序列化 JSON 字符串为目标类型。
|
* 反序列化 JSON 字符串为目标类型。
|
||||||
* @param jsonString JSON 字符串。
|
* @param jsonString JSON 字符串。
|
||||||
* @param options 序列化选项(默认 JsonOption.instance)。
|
* @param options 序列化选项(默认 JsonOption.INSTANCE)。
|
||||||
* @return T 实例(要求 T 有无参构造,字段为 var)。
|
* @return T 实例(要求 T 有无参构造,字段为 var)。
|
||||||
|
* @throws Exception JSON 格式非法、类型不匹配或目标类型不支持时抛出。
|
||||||
*/
|
*/
|
||||||
public static func Deserialize<T>(jsonString: String, options: JsonOption): T {
|
public static func deserialize<T>(jsonString: String, options: JsonOption): T {
|
||||||
let value = JsonValue.fromStr(jsonString)
|
let value = JsonValue.fromStr(jsonString)
|
||||||
JsonReader.read<T>(value, options)
|
JsonReader.read<T>(value, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 反序列化 JSON 字符串为目标类型(默认选项)。
|
* 反序列化 JSON 字符串为目标类型(默认选项)。
|
||||||
|
* @param jsonString JSON 字符串。
|
||||||
|
* @return T 实例(要求 T 有无参构造,字段为 var)。
|
||||||
|
* @throws Exception JSON 格式非法、类型不匹配或目标类型不支持时抛出。
|
||||||
*/
|
*/
|
||||||
public static func Deserialize<T>(jsonString: String): T {
|
public static func deserialize<T>(jsonString: String): T {
|
||||||
Deserialize<T>(jsonString, JsonOption.instance)
|
deserialize<T>(jsonString, JsonOption.INSTANCE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按运行时类型信息反序列化(供框架运行时绑定等场景使用,无需编译期泛型)。
|
* 按运行时类型信息反序列化(供框架运行时绑定等场景使用)。
|
||||||
* @param typeInfo 目标类型(运行时)。
|
* @param typeInfo 目标类型(运行时,决定构造哪种类型)。
|
||||||
* @param jsonString JSON 字符串。
|
* @param jsonString JSON 字符串。
|
||||||
* @param options 序列化选项。
|
* @param options 序列化选项。
|
||||||
* @return 反序列化后的实例(Any)。
|
* @return T 实例;若实际结果与 T 不匹配抛出异常。
|
||||||
|
* @throws Exception JSON 格式非法、类型不匹配或目标类型不支持时抛出。
|
||||||
*/
|
*/
|
||||||
public static func Deserialize(typeInfo: TypeInfo, jsonString: String, options: JsonOption): Any {
|
public static func deserialize<T>(typeInfo: TypeInfo, jsonString: String, options: JsonOption): T {
|
||||||
let value = JsonValue.fromStr(jsonString)
|
let value = JsonValue.fromStr(jsonString)
|
||||||
JsonReader.read(typeInfo, value, options)
|
let result = JsonReader.read(typeInfo, value, options)
|
||||||
|
if (let t: T <- result) {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
throw Exception("JsonSerializer: 运行时绑定反序列化结果类型不匹配")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按运行时类型信息反序列化(默认选项)。
|
* 按运行时类型信息反序列化(默认选项)。
|
||||||
|
* @param typeInfo 目标类型(运行时,决定构造哪种类型)。
|
||||||
|
* @param jsonString JSON 字符串。
|
||||||
|
* @return T 实例;若实际结果与 T 不匹配抛出异常。
|
||||||
|
* @throws Exception JSON 格式非法、类型不匹配或目标类型不支持时抛出。
|
||||||
*/
|
*/
|
||||||
public static func Deserialize(typeInfo: TypeInfo, jsonString: String): Any {
|
public static func deserialize<T>(typeInfo: TypeInfo, jsonString: String): T {
|
||||||
Deserialize(typeInfo, jsonString, JsonOption.instance)
|
deserialize<T>(typeInfo, jsonString, JsonOption.INSTANCE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-51
@@ -8,10 +8,21 @@
|
|||||||
|
|
||||||
package simcu::serialization.json
|
package simcu::serialization.json
|
||||||
|
|
||||||
import std.collection.*
|
import std.collection.ArrayList
|
||||||
import std.reflect.*
|
import std.collection.HashMap
|
||||||
import std.time.*
|
import std.reflect.ClassTypeInfo
|
||||||
import stdx.encoding.json.*
|
import std.reflect.EnumTypeInfo
|
||||||
|
import std.reflect.InstanceFunctionInfo
|
||||||
|
import std.reflect.TypeInfo
|
||||||
|
import std.time.DateTime
|
||||||
|
import stdx.encoding.json.JsonArray
|
||||||
|
import stdx.encoding.json.JsonBool
|
||||||
|
import stdx.encoding.json.JsonFloat
|
||||||
|
import stdx.encoding.json.JsonInt
|
||||||
|
import stdx.encoding.json.JsonNull
|
||||||
|
import stdx.encoding.json.JsonObject
|
||||||
|
import stdx.encoding.json.JsonString
|
||||||
|
import stdx.encoding.json.JsonValue
|
||||||
import simcu::serialization.common.ReflectionCache
|
import simcu::serialization.common.ReflectionCache
|
||||||
import simcu::serialization.common.FieldMetadata
|
import simcu::serialization.common.FieldMetadata
|
||||||
|
|
||||||
@@ -25,6 +36,10 @@ public class JsonWriter {
|
|||||||
writeValue(obj, options, 0)
|
writeValue(obj, options, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将任意值递归写为 JsonValue。
|
||||||
|
* @throws Exception 深度超限或类型不支持时。
|
||||||
|
*/
|
||||||
private static func writeValue(value: Any, options: JsonOption, depth: Int64): JsonValue {
|
private static func writeValue(value: Any, options: JsonOption, depth: Int64): JsonValue {
|
||||||
if (depth > options.maxDepth) {
|
if (depth > options.maxDepth) {
|
||||||
throw Exception("JsonSerializer: 超过最大递归深度 ${options.maxDepth}")
|
throw Exception("JsonSerializer: 超过最大递归深度 ${options.maxDepth}")
|
||||||
@@ -105,17 +120,26 @@ public class JsonWriter {
|
|||||||
obj
|
obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反射写入对象字段。
|
||||||
|
* @throws Exception 字段缺少变量句柄时。
|
||||||
|
*/
|
||||||
private static func writeObject(value: Any, ct: ClassTypeInfo, options: JsonOption, depth: Int64): JsonValue {
|
private static func writeObject(value: Any, ct: ClassTypeInfo, options: JsonOption, depth: Int64): JsonValue {
|
||||||
let obj = JsonObject()
|
let obj = JsonObject()
|
||||||
// 1) 子类自身字段(反射)
|
// 1) 子类自身字段(反射)
|
||||||
let fields = ReflectionCache.getFields(ct, options.propertyNamingPolicy)
|
let fields = ReflectionCache.getFields(ct, options.propertyNamingPolicy)
|
||||||
for (f in fields) {
|
for (f in fields) {
|
||||||
// 只要不是 @SerializerIgnore 的字段一律输出;None 字段输出 null
|
// 只要不是 @Ignore 的字段一律输出;None 字段输出 null
|
||||||
if (f.ignore) {
|
if (f.ignore) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let raw = f.variable.getOrThrow().getValue(value)
|
match (f.variable) {
|
||||||
obj.put(f.jsonName, writeValue(raw, options, depth + 1))
|
case Some(variable) =>
|
||||||
|
let raw = variable.getValue(value)
|
||||||
|
obj.put(f.jsonName, writeValue(raw, options, depth + 1))
|
||||||
|
case None =>
|
||||||
|
throw Exception("JsonSerializer: 字段 ${f.jsonName} 缺少变量句柄")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 2) 父类字段(@SerializerParent 宏生成的静态导出方法,沿继承链逐层调用)
|
// 2) 父类字段(@SerializerParent 宏生成的静态导出方法,沿继承链逐层调用)
|
||||||
writeParentFields(value, ct, obj, options, depth)
|
writeParentFields(value, ct, obj, options, depth)
|
||||||
@@ -138,45 +162,62 @@ public class JsonWriter {
|
|||||||
obj.put(k, writeValue(v, options, depth + 1))
|
obj.put(k, writeValue(v, options, depth + 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
// 调用失败(不应发生)→ 跳过该层
|
// 调用失败(不应发生)→ 跳过该层
|
||||||
|
let _ = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parent = p.superClass
|
parent = p.superClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 反射迭代集合:优先 toArray 转数组再 get(index);无 toArray(如 Array)直接用原集合
|
/// 反射迭代集合:优先 toArray 转数组再 get(index);无 toArray(如 Array)直接用原集合。
|
||||||
|
/// @throws Exception 集合迭代超限(超过 100 万次)时。
|
||||||
private static func forEachElement(collection: Any, action: (Any) -> Unit): Unit {
|
private static func forEachElement(collection: Any, action: (Any) -> Unit): Unit {
|
||||||
let ti = TypeInfo.of(collection)
|
|
||||||
var arr: Any = collection
|
var arr: Any = collection
|
||||||
try {
|
try {
|
||||||
|
let ti = TypeInfo.of(collection)
|
||||||
arr = ti.getInstanceFunction("toArray", []).apply(collection, [])
|
arr = ti.getInstanceFunction("toArray", []).apply(collection, [])
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
// 无 toArray(如 Array):直接用原集合 get(i)
|
// 无 toArray(如 Array):直接用原集合 get(i)
|
||||||
|
let _ = e
|
||||||
}
|
}
|
||||||
let arrTi = TypeInfo.of(arr)
|
let arrTi = TypeInfo.of(arr)
|
||||||
// 仓颉集合 get(index) 返回 Option<T>:Some(元素) / None(越界结束)
|
// 仓颉集合 get(index) 返回 Option<T>:Some(元素) / None(越界结束)
|
||||||
let getFunc = arrTi.getInstanceFunction("get", [TypeInfo.of<Int64>()])
|
let getFunc = arrTi.getInstanceFunction("get", [TypeInfo.of<Int64>()])
|
||||||
|
iterateElements(arr, getFunc, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 反射迭代集合元素:get(i) 越界(抛异常或返回 None)视为迭代结束;
|
||||||
|
/// 超过 100 万次仍不结束抛异常。getFunc 为只查询一次的 get 函数句柄,作为参数传入。
|
||||||
|
private static func iterateElements(arr: Any, getFunc: InstanceFunctionInfo, action: (Any) -> Unit): Unit {
|
||||||
var i: Int64 = 0
|
var i: Int64 = 0
|
||||||
var guard: Int64 = 0
|
var done = false
|
||||||
while (true) {
|
while (i < 1000000 && !done) {
|
||||||
guard += 1
|
match (getElemAt(getFunc, arr, i)) {
|
||||||
if (guard > 1000000) {
|
case None => done = true
|
||||||
throw Exception("JsonSerializer: 集合迭代超限 type=${TypeInfo.of(arr)}")
|
case Some(inner) =>
|
||||||
|
if (let Some(v) <- unwrapOptionValue(inner)) {
|
||||||
|
action(v)
|
||||||
|
} else {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var elem: ?Any = None
|
if (!done) {
|
||||||
try {
|
i += 1
|
||||||
elem = Some(getFunc.apply(arr, [i]))
|
|
||||||
} catch (_: Exception) {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if (let Some(v) <- unwrapOptionValue(elem.getOrThrow())) {
|
}
|
||||||
action(v)
|
if (i >= 1000000 && !done) {
|
||||||
} else {
|
throw Exception("JsonSerializer: 集合迭代超限 type=${TypeInfo.of(arr)}")
|
||||||
break
|
}
|
||||||
}
|
}
|
||||||
i += 1
|
|
||||||
|
/// 反射 get(i):越界抛异常视为 None(迭代结束)。
|
||||||
|
private static func getElemAt(getFunc: InstanceFunctionInfo, arr: Any, k: Int64): ?Any {
|
||||||
|
try {
|
||||||
|
Some(getFunc.apply(arr, [k]))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,32 +244,40 @@ public class JsonWriter {
|
|||||||
|
|
||||||
/// 从 "Array<String>" / "std.collection.HashMap<String, Any>" / "Option<Int64>" 提取类型参数
|
/// 从 "Array<String>" / "std.collection.HashMap<String, Any>" / "Option<Int64>" 提取类型参数
|
||||||
public static func extractTypeArgs(typeName: String): ArrayList<String> {
|
public static func extractTypeArgs(typeName: String): ArrayList<String> {
|
||||||
var args = ArrayList<String>()
|
let args = ArrayList<String>()
|
||||||
match (typeName.indexOf("<")) {
|
match (typeName.indexOf("<")) {
|
||||||
case None => return args
|
case None => ()
|
||||||
case Some(i) =>
|
case Some(i) => scanTypeArgs(typeName, i + 1, 0, i + 1, args)
|
||||||
var depth: Int64 = 0
|
|
||||||
var start = i + 1
|
|
||||||
var j = i + 1
|
|
||||||
while (j < typeName.size) {
|
|
||||||
let b = typeName[j]
|
|
||||||
if (b == 0x3Cu8) { // '<'
|
|
||||||
depth += 1
|
|
||||||
} else if (b == 0x3Eu8) { // '>'
|
|
||||||
if (depth == 0) {
|
|
||||||
args.add(typeName[start..j])
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
depth -= 1
|
|
||||||
} else if (b == 0x2Cu8) { // ','
|
|
||||||
if (depth == 0) {
|
|
||||||
args.add(typeName[start..j])
|
|
||||||
start = j + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
args
|
args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 递归逐字符扫描类型参数列表(等价于原 while 版本)。
|
||||||
|
/// j 为当前扫描位置,depth 为 '<' 嵌套深度,start 为当前待收集参数起点;
|
||||||
|
/// 遇到 depth==0 的 '>'(外层闭合)时收集最后一个参数并结束。
|
||||||
|
private static func scanTypeArgs(typeName: String, j: Int64, depth: Int64, start: Int64,
|
||||||
|
args: ArrayList<String>): Unit {
|
||||||
|
if (j >= typeName.size) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let b = typeName[j]
|
||||||
|
if (b == 0x3Cu8) { // '<'
|
||||||
|
scanTypeArgs(typeName, j + 1, depth + 1, start, args)
|
||||||
|
} else if (b == 0x3Eu8) { // '>'
|
||||||
|
if (depth == 0) {
|
||||||
|
args.add(typeName[start..j])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scanTypeArgs(typeName, j + 1, depth - 1, start, args)
|
||||||
|
} else if (b == 0x2Cu8) { // ','
|
||||||
|
if (depth == 0) {
|
||||||
|
args.add(typeName[start..j])
|
||||||
|
scanTypeArgs(typeName, j + 1, depth, j + 1, args)
|
||||||
|
} else {
|
||||||
|
scanTypeArgs(typeName, j + 1, depth, start, args)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scanTypeArgs(typeName, j + 1, depth, start, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,17 +12,20 @@
|
|||||||
* @SerializerParent
|
* @SerializerParent
|
||||||
* class BaseUser {
|
* class BaseUser {
|
||||||
* public var _id: String = ""
|
* public var _id: String = ""
|
||||||
* @SerializerPropertyName["userId"]
|
* @PropertyName["userId"]
|
||||||
* public var _userId: String = ""
|
* public var _userId: String = ""
|
||||||
* @SerializerIgnore
|
* @Ignore
|
||||||
* public var _temp: String = ""
|
* public var _temp: String = ""
|
||||||
|
* @NotRequired
|
||||||
|
* public var _name: String = ""
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* 行为:
|
* 行为:
|
||||||
* - 非 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
|
||||||
* - @SerializerIgnore 字段跳过;@SerializerPropertyName 用注解名做 JSON 键
|
* - @Ignore 字段跳过;@PropertyName 用注解名做 JSON 键
|
||||||
|
* - 默认所有 var 字段必填(反序列化要求 JSON 键存在);@NotRequired 字段允许缺失
|
||||||
*/
|
*/
|
||||||
|
|
||||||
macro package simcu::serialization.macros
|
macro package simcu::serialization.macros
|
||||||
@@ -36,6 +39,7 @@ private class FieldInfo {
|
|||||||
public var jsonName: String = "" // JSON 键名(注解优先,否则字段名)
|
public var jsonName: String = "" // JSON 键名(注解优先,否则字段名)
|
||||||
public var typeName: String = "" // 字段类型名(如 String)
|
public var typeName: String = "" // 字段类型名(如 String)
|
||||||
public var isVar: Bool = true // var 可变(可导入);let 只导出
|
public var isVar: Bool = true // var 可变(可导入);let 只导出
|
||||||
|
public var required: Bool = true // 默认必填;@NotRequired 则允许缺失
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
@@ -45,6 +49,27 @@ private class FieldInfo {
|
|||||||
this.typeName = typeName
|
this.typeName = typeName
|
||||||
this.isVar = isVar
|
this.isVar = isVar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(fieldName: String, jsonName: String, typeName: String, isVar: Bool, required: Bool) {
|
||||||
|
this.fieldName = fieldName
|
||||||
|
this.jsonName = jsonName
|
||||||
|
this.typeName = typeName
|
||||||
|
this.isVar = isVar
|
||||||
|
this.required = required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 单个注解的信息(名称 + 参数 tokens)
|
||||||
|
private class AnnotInfo {
|
||||||
|
public var name: String = "" // 注解名(如 PropertyName)
|
||||||
|
public var attrs: String = "" // 注解参数 tokens(如 "user_alias",含引号)
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public init(name: String, attrs: String) {
|
||||||
|
this.name = name
|
||||||
|
this.attrs = attrs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,7 +110,7 @@ public macro SerializerParent(input: Tokens): Tokens {
|
|||||||
throw ASTException("@SerializerParent 只能标注在 class 声明上(不支持 struct/interface/enum)")
|
throw ASTException("@SerializerParent 只能标注在 class 声明上(不支持 struct/interface/enum)")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 收集类体内的可序列化字段(任意 var/let 字段 + 类型注解;跳过 @SerializerIgnore;@SerializerPropertyName 用注解名)
|
/// 收集类体内的可序列化字段(任意 var/let 字段 + 类型注解;跳过 @Ignore;@PropertyName 用注解名;默认必填,@NotRequired 允许缺失)
|
||||||
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,20 +121,25 @@ 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 =>
|
||||||
// 带注解的字段:@SerializerIgnore 跳过;@SerializerPropertyName["x"] 取注解名
|
// 带注解的字段:@Ignore 跳过;@PropertyName["x"] 取注解名;@NotRequired 允许缺失
|
||||||
if (md.identifier.value == "SerializerIgnore") {
|
let annots = collectAnnotations(md)
|
||||||
|
if (hasAnnotation(annots, "Ignore")) {
|
||||||
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 == "SerializerPropertyName") {
|
var jsonName = inner.identifier.value
|
||||||
// 属性 tokens 形如 "user_alias"(带引号),去掉两端引号
|
var required = true // 默认必填
|
||||||
stripQuotes(md.macroAttrs.toString())
|
for (an in annots) {
|
||||||
} else {
|
if (an.name == "PropertyName") {
|
||||||
inner.identifier.value
|
// 属性 tokens 形如 "user_alias"(带引号),去掉两端引号
|
||||||
|
jsonName = stripQuotes(an.attrs)
|
||||||
|
} else if (an.name == "NotRequired") {
|
||||||
|
required = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result.add(FieldInfo(inner.identifier.value, jsonName,
|
result.add(FieldInfo(inner.identifier.value, jsonName,
|
||||||
inner.declType.toTokens().toString(), inner.keyword.kind == TokenKind.VAR))
|
inner.declType.toTokens().toString(), inner.keyword.kind == TokenKind.VAR, required))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case _ => ()
|
case _ => ()
|
||||||
@@ -118,6 +148,30 @@ private func collectFields(cd: ClassDecl): ArrayList<FieldInfo> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 收集字段上全部注解(含嵌套,支持 @NotRequired 与 @PropertyName 组合)。
|
||||||
|
/// 手动记录最外层注解后再遍历内部,避免遍历器是否含根节点的不确定性;
|
||||||
|
/// 重复收集同名注解对后续「是否存在」与「取哪个参数」判断是幂等的。
|
||||||
|
private func collectAnnotations(md: MacroExpandDecl): ArrayList<AnnotInfo> {
|
||||||
|
var result = ArrayList<AnnotInfo>()
|
||||||
|
result.add(AnnotInfo(md.identifier.value, md.macroAttrs.toString()))
|
||||||
|
let visitor = AnnotVisitor()
|
||||||
|
md.traverse(visitor)
|
||||||
|
for (a in visitor.getAnnotations()) {
|
||||||
|
result.add(a)
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 注解列表中是否含指定注解名。
|
||||||
|
private func hasAnnotation(annots: ArrayList<AnnotInfo>, name: String): Bool {
|
||||||
|
for (an in annots) {
|
||||||
|
if (an.name == name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// 普通字段是否可序列化:有类型注解(不要求 _ 前缀)
|
/// 普通字段是否可序列化:有类型注解(不要求 _ 前缀)
|
||||||
private func isSerializableField(vd: VarDecl): Bool {
|
private func isSerializableField(vd: VarDecl): Bool {
|
||||||
!vd.colon.value.isEmpty()
|
!vd.colon.value.isEmpty()
|
||||||
@@ -144,7 +198,20 @@ private class FieldVisitor <: Visitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 去掉字符串两端引号(SerializerPropertyName 属性 tokens 形如 "user_alias")
|
/// 遍历器:收集 MacroExpandDecl 子树内所有注解(用于识别 @PropertyName / @Ignore / @NotRequired)
|
||||||
|
private class AnnotVisitor <: Visitor {
|
||||||
|
private var _annotations = ArrayList<AnnotInfo>()
|
||||||
|
|
||||||
|
public override func visit(decl: MacroExpandDecl): Unit {
|
||||||
|
_annotations.add(AnnotInfo(decl.identifier.value, decl.macroAttrs.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getAnnotations(): ArrayList<AnnotInfo> {
|
||||||
|
_annotations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 去掉字符串两端引号(PropertyName 属性 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]
|
||||||
@@ -181,6 +248,14 @@ private func buildImportSource(className: String, fields: ArrayList<FieldInfo>):
|
|||||||
sb.append(" }\n")
|
sb.append(" }\n")
|
||||||
}
|
}
|
||||||
sb.append(" }\n")
|
sb.append(" }\n")
|
||||||
|
// 默认必填:JSON 键必须存在,否则抛异常(@NotRequired 字段已置 required=false)
|
||||||
|
for (f in fields) {
|
||||||
|
if (f.isVar && f.required) {
|
||||||
|
sb.append(" if (!json.contains(\"${f.jsonName}\")) {\n")
|
||||||
|
sb.append(" throw Exception(\"JsonSerializer: 缺少必填字段 \\\"${f.jsonName}\\\"\")\n")
|
||||||
|
sb.append(" }\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
sb.append(" }\n")
|
sb.append(" }\n")
|
||||||
sb.toString()
|
sb.toString()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||||
* JsonSerializer 单元测试(cjpm test)。
|
* JsonSerializer 单元测试(cjpm test)。
|
||||||
* 覆盖:基础类型 / 对象字段输出 / 注解(SerializerPropertyName、SerializerIgnore)/ Option /
|
* 覆盖:基础类型 / 对象字段输出 / 注解(PropertyName、Ignore、NotRequired)/ Option /
|
||||||
* 集合(Array、ArrayList、HashSet)/ HashMap / 枚举 / 命名策略 /
|
* 集合(Array、ArrayList、HashSet)/ HashMap / 枚举 / 命名策略 /
|
||||||
* 继承(平台限制)/ maxDepth / 宽松类型转换。
|
* 继承(平台限制)/ maxDepth / 宽松类型转换。
|
||||||
*
|
*
|
||||||
@@ -10,12 +10,22 @@
|
|||||||
|
|
||||||
package simcu::serialization.tests
|
package simcu::serialization.tests
|
||||||
|
|
||||||
import std.collection.*
|
import std.collection.ArrayList
|
||||||
import std.time.*
|
import std.collection.HashMap
|
||||||
|
import std.collection.HashSet
|
||||||
|
import std.time.DateTime
|
||||||
import std.unittest.*
|
import std.unittest.*
|
||||||
import std.unittest.testmacro.*
|
import std.unittest.testmacro.Expect
|
||||||
import simcu::serialization.*
|
import std.unittest.testmacro.TestCase
|
||||||
import simcu::serialization.macros.*
|
import std.unittest.testmacro.Test
|
||||||
|
import simcu::serialization.JsonOption
|
||||||
|
import simcu::serialization.JsonSerializer
|
||||||
|
import simcu::serialization.PropertyNamingPolicy
|
||||||
|
import simcu::serialization.ReflectionCache
|
||||||
|
import simcu::serialization.Ignore
|
||||||
|
import simcu::serialization.NotRequired
|
||||||
|
import simcu::serialization.PropertyName
|
||||||
|
import simcu::serialization.macros.SerializerParent
|
||||||
enum Role {
|
enum Role {
|
||||||
| Admin
|
| Admin
|
||||||
| Member
|
| Member
|
||||||
@@ -34,9 +44,9 @@ class Address {
|
|||||||
open class BaseUser {
|
open class BaseUser {
|
||||||
public var _id: String = ""
|
public var _id: String = ""
|
||||||
public var _createdAt: String = ""
|
public var _createdAt: String = ""
|
||||||
@SerializerIgnore
|
@Ignore
|
||||||
public var _temp: String = ""
|
public var _temp: String = ""
|
||||||
@SerializerPropertyName["parent_alias"]
|
@PropertyName["parent_alias"]
|
||||||
public var _parentAlias: String = ""
|
public var _parentAlias: String = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +60,9 @@ 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() // 无下划线(嵌套对象)
|
||||||
@SerializerPropertyName["user_alias"]
|
@PropertyName["user_alias"]
|
||||||
public var _alias: String = "" // 下划线 + 注解覆盖
|
public var _alias: String = "" // 下划线 + 注解覆盖
|
||||||
@SerializerIgnore
|
@Ignore
|
||||||
public var temp: String = "secret" // 无下划线 + 忽略
|
public var temp: String = "secret" // 无下划线 + 忽略
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +73,25 @@ class Event {
|
|||||||
public var _optTime: ?DateTime = None
|
public var _optTime: ?DateTime = None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 默认必填语义测试:默认所有字段必填;标 @NotRequired 的字段反序列化时允许缺失
|
||||||
|
class RequiredUser {
|
||||||
|
public var _id: String = "" // 默认必填:键缺失 → 抛异常
|
||||||
|
@NotRequired
|
||||||
|
public var _name: String = "" // @NotRequired:键缺失 → 允许,保持默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认必填 + @SerializerParent:父类默认必填字段缺失时导入抛异常;@NotRequired 父类字段允许缺失
|
||||||
|
@SerializerParent
|
||||||
|
open class ReqBase {
|
||||||
|
public var _rid: String = "" // 默认必填
|
||||||
|
@NotRequired
|
||||||
|
public var _extra: String = "" // @NotRequired:允许缺失
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReqChild <: ReqBase {
|
||||||
|
public var _own: String = "" // 默认必填
|
||||||
|
}
|
||||||
|
|
||||||
// 多层继承:爷(标宏)→ 父(不标宏)→ 子
|
// 多层继承:爷(标宏)→ 父(不标宏)→ 子
|
||||||
@SerializerParent
|
@SerializerParent
|
||||||
open class GrandParent {
|
open class GrandParent {
|
||||||
@@ -131,30 +160,30 @@ public class JsonSerializerTests {
|
|||||||
/// 基础类型序列化
|
/// 基础类型序列化
|
||||||
@TestCase
|
@TestCase
|
||||||
public func basicTypes(): Unit {
|
public func basicTypes(): Unit {
|
||||||
@Expect(JsonSerializer.Serialize(Int64(42)), "42")
|
@Expect(JsonSerializer.serialize(Int64(42)), "42")
|
||||||
@Expect(JsonSerializer.Serialize(Int32(7)), "7")
|
@Expect(JsonSerializer.serialize(Int32(7)), "7")
|
||||||
@Expect(JsonSerializer.Serialize(Int16(3)), "3")
|
@Expect(JsonSerializer.serialize(Int16(3)), "3")
|
||||||
@Expect(JsonSerializer.Serialize(UInt32(9)), "9")
|
@Expect(JsonSerializer.serialize(UInt32(9)), "9")
|
||||||
@Expect(JsonSerializer.Serialize("hi"), "\"hi\"")
|
@Expect(JsonSerializer.serialize("hi"), "\"hi\"")
|
||||||
@Expect(JsonSerializer.Serialize(true), "true")
|
@Expect(JsonSerializer.serialize(true), "true")
|
||||||
@Expect(JsonSerializer.Serialize(false), "false")
|
@Expect(JsonSerializer.serialize(false), "false")
|
||||||
@Expect(JsonSerializer.Serialize(Float64(3.5)), "3.500000")
|
@Expect(JsonSerializer.serialize(Float64(3.5)), "3.500000")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 对象序列化:下划线保留、注解生效、忽略生效、None→null、父类字段不输出
|
/// 对象序列化:下划线保留、注解生效、忽略生效、None→null、父类字段不输出
|
||||||
@TestCase
|
@TestCase
|
||||||
public func objectSerialize(): Unit {
|
public func objectSerialize(): Unit {
|
||||||
let json = JsonSerializer.Serialize(makeUser())
|
let json = JsonSerializer.serialize(makeUser())
|
||||||
// 下划线字段原样输出
|
// 下划线字段原样输出
|
||||||
@Expect(json.contains("\"_name\": \"alice\""), true)
|
@Expect(json.contains("\"_name\": \"alice\""), true)
|
||||||
@Expect(json.contains("\"_age\": 30"), true)
|
@Expect(json.contains("\"_age\": 30"), true)
|
||||||
// 无下划线字段原样输出
|
// 无下划线字段原样输出
|
||||||
@Expect(json.contains("\"score\""), true)
|
@Expect(json.contains("\"score\""), true)
|
||||||
@Expect(json.contains("\"active\": true"), true)
|
@Expect(json.contains("\"active\": true"), true)
|
||||||
// @SerializerPropertyName 覆盖字段名
|
// @PropertyName 覆盖字段名
|
||||||
@Expect(json.contains("\"user_alias\": \"xiaoming\""), true)
|
@Expect(json.contains("\"user_alias\": \"xiaoming\""), true)
|
||||||
@Expect(json.contains("\"_alias\""), false)
|
@Expect(json.contains("\"_alias\""), false)
|
||||||
// @SerializerIgnore 字段不输出
|
// @Ignore 字段不输出
|
||||||
@Expect(json.contains("temp"), false)
|
@Expect(json.contains("temp"), false)
|
||||||
@Expect(json.contains("\"secret\""), false)
|
@Expect(json.contains("\"secret\""), false)
|
||||||
// Option:Some→值,None→null
|
// Option:Some→值,None→null
|
||||||
@@ -165,21 +194,21 @@ 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)
|
||||||
// 父类字段(@SerializerParent 宏):_id/_createdAt 输出,@SerializerIgnore 父类字段不输出
|
// 父类字段(@SerializerParent 宏):_id/_createdAt 输出,@Ignore 父类字段不输出
|
||||||
@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)
|
||||||
// 父类 @SerializerPropertyName["parent_alias"] 生效
|
// 父类 @PropertyName["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)
|
||||||
// 父类 @SerializerIgnore _temp 不输出
|
// 父类 @Ignore _temp 不输出
|
||||||
@Expect(json.contains("secret-parent"), false)
|
@Expect(json.contains("secret-parent"), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 对象反序列化往返
|
/// 对象反序列化往返
|
||||||
@TestCase
|
@TestCase
|
||||||
public func objectRoundTrip(): Unit {
|
public func objectRoundTrip(): Unit {
|
||||||
let json = JsonSerializer.Serialize(makeUser())
|
let json = JsonSerializer.serialize(makeUser())
|
||||||
let u2 = JsonSerializer.Deserialize<User>(json)
|
let u2 = JsonSerializer.deserialize<User>(json)
|
||||||
@Expect(u2._name, "alice")
|
@Expect(u2._name, "alice")
|
||||||
@Expect(u2._age, Int64(30))
|
@Expect(u2._age, Int64(30))
|
||||||
@Expect(u2.active, true)
|
@Expect(u2.active, true)
|
||||||
@@ -187,7 +216,7 @@ 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") // @SerializerIgnore:反序列化不改动默认值
|
@Expect(u2.temp, "secret") // @Ignore:反序列化不改动默认值
|
||||||
// 父类字段往返(@SerializerParent 宏)
|
// 父类字段往返(@SerializerParent 宏)
|
||||||
@Expect(u2._id, "u-1")
|
@Expect(u2._id, "u-1")
|
||||||
@Expect(u2._createdAt, "2025-01-01")
|
@Expect(u2._createdAt, "2025-01-01")
|
||||||
@@ -210,8 +239,8 @@ public class JsonSerializerTests {
|
|||||||
/// Array 往返
|
/// Array 往返
|
||||||
@TestCase
|
@TestCase
|
||||||
public func arrayRoundTrip(): Unit {
|
public func arrayRoundTrip(): Unit {
|
||||||
let json = JsonSerializer.Serialize(["x", "y", "z"])
|
let json = JsonSerializer.serialize(["x", "y", "z"])
|
||||||
let arrBack = JsonSerializer.Deserialize<Array<String>>(json)
|
let arrBack = JsonSerializer.deserialize<Array<String>>(json)
|
||||||
@Expect(arrBack.size, Int64(3))
|
@Expect(arrBack.size, Int64(3))
|
||||||
@Expect(arrBack[0], "x")
|
@Expect(arrBack[0], "x")
|
||||||
@Expect(arrBack[2], "z")
|
@Expect(arrBack[2], "z")
|
||||||
@@ -223,8 +252,8 @@ public class JsonSerializerTests {
|
|||||||
var list = ArrayList<String>()
|
var list = ArrayList<String>()
|
||||||
list.add("a")
|
list.add("a")
|
||||||
list.add("b")
|
list.add("b")
|
||||||
let json = JsonSerializer.Serialize(list)
|
let json = JsonSerializer.serialize(list)
|
||||||
let back = JsonSerializer.Deserialize<ArrayList<String>>(json)
|
let back = JsonSerializer.deserialize<ArrayList<String>>(json)
|
||||||
@Expect(back.size, Int64(2))
|
@Expect(back.size, Int64(2))
|
||||||
@Expect(back[0], "a")
|
@Expect(back[0], "a")
|
||||||
@Expect(back[1], "b")
|
@Expect(back[1], "b")
|
||||||
@@ -236,8 +265,8 @@ public class JsonSerializerTests {
|
|||||||
var set = HashSet<String>()
|
var set = HashSet<String>()
|
||||||
set.add("x")
|
set.add("x")
|
||||||
set.add("y")
|
set.add("y")
|
||||||
let json = JsonSerializer.Serialize(set)
|
let json = JsonSerializer.serialize(set)
|
||||||
let back = JsonSerializer.Deserialize<HashSet<String>>(json)
|
let back = JsonSerializer.deserialize<HashSet<String>>(json)
|
||||||
@Expect(back.contains("x"), true)
|
@Expect(back.contains("x"), true)
|
||||||
@Expect(back.contains("y"), true)
|
@Expect(back.contains("y"), true)
|
||||||
}
|
}
|
||||||
@@ -248,8 +277,8 @@ public class JsonSerializerTests {
|
|||||||
let m = HashMap<String, Any>()
|
let m = HashMap<String, Any>()
|
||||||
m["k1"] = "v1"
|
m["k1"] = "v1"
|
||||||
m["k2"] = Int64(42)
|
m["k2"] = Int64(42)
|
||||||
let json = JsonSerializer.Serialize(m)
|
let json = JsonSerializer.serialize(m)
|
||||||
let mBack = JsonSerializer.Deserialize<HashMap<String, Any>>(json)
|
let mBack = JsonSerializer.deserialize<HashMap<String, Any>>(json)
|
||||||
@Expect(toStr(mBack["k1"]), "v1")
|
@Expect(toStr(mBack["k1"]), "v1")
|
||||||
@Expect(toStr(mBack["k2"]), "42")
|
@Expect(toStr(mBack["k2"]), "42")
|
||||||
}
|
}
|
||||||
@@ -259,15 +288,15 @@ public class JsonSerializerTests {
|
|||||||
public func namingPolicies(): Unit {
|
public func namingPolicies(): Unit {
|
||||||
let optNone = JsonOption()
|
let optNone = JsonOption()
|
||||||
optNone.propertyNamingPolicy = PropertyNamingPolicy.Keep
|
optNone.propertyNamingPolicy = PropertyNamingPolicy.Keep
|
||||||
let jsonNone = JsonSerializer.Serialize(makeUser(), optNone)
|
let jsonNone = JsonSerializer.serialize(makeUser(), optNone)
|
||||||
|
|
||||||
let optCamel = JsonOption()
|
let optCamel = JsonOption()
|
||||||
optCamel.propertyNamingPolicy = PropertyNamingPolicy.CamelCase
|
optCamel.propertyNamingPolicy = PropertyNamingPolicy.CamelCase
|
||||||
let jsonCamel = JsonSerializer.Serialize(makeUser(), optCamel)
|
let jsonCamel = JsonSerializer.serialize(makeUser(), optCamel)
|
||||||
|
|
||||||
let optSnake = JsonOption()
|
let optSnake = JsonOption()
|
||||||
optSnake.propertyNamingPolicy = PropertyNamingPolicy.SnakeCase
|
optSnake.propertyNamingPolicy = PropertyNamingPolicy.SnakeCase
|
||||||
let jsonSnake = JsonSerializer.Serialize(makeUser(), optSnake)
|
let jsonSnake = JsonSerializer.serialize(makeUser(), optSnake)
|
||||||
|
|
||||||
// 模型字段全为小写/下划线,三种策略结果应一致
|
// 模型字段全为小写/下划线,三种策略结果应一致
|
||||||
@Expect(jsonNone, jsonCamel)
|
@Expect(jsonNone, jsonCamel)
|
||||||
@@ -292,20 +321,20 @@ public class JsonSerializerTests {
|
|||||||
/// 枚举序列化/反序列化
|
/// 枚举序列化/反序列化
|
||||||
@TestCase
|
@TestCase
|
||||||
public func enumSerialize(): Unit {
|
public func enumSerialize(): Unit {
|
||||||
@Expect(JsonSerializer.Serialize(Role.Admin), "\"Admin\"")
|
@Expect(JsonSerializer.serialize(Role.Admin), "\"Admin\"")
|
||||||
@Expect(JsonSerializer.Serialize(Role.Guest), "\"Guest\"")
|
@Expect(JsonSerializer.serialize(Role.Guest), "\"Guest\"")
|
||||||
let u = User()
|
let u = User()
|
||||||
u._role = Role.Guest
|
u._role = Role.Guest
|
||||||
let back = JsonSerializer.Deserialize<User>(JsonSerializer.Serialize(u))
|
let back = JsonSerializer.deserialize<User>(JsonSerializer.serialize(u))
|
||||||
@Expect(roleStr(back._role), "Guest")
|
@Expect(roleStr(back._role), "Guest")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Option 字段:null → None,值 → Some
|
/// Option 字段:null → None,值 → Some
|
||||||
@TestCase
|
@TestCase
|
||||||
public func optionField(): Unit {
|
public func optionField(): Unit {
|
||||||
let none: ?String = JsonSerializer.Deserialize<?String>("null")
|
let none: ?String = JsonSerializer.deserialize<?String>("null")
|
||||||
@Expect(none.isNone(), true)
|
@Expect(none.isNone(), true)
|
||||||
let some: ?String = JsonSerializer.Deserialize<?String>("\"hi\"")
|
let some: ?String = JsonSerializer.deserialize<?String>("\"hi\"")
|
||||||
@Expect(some.isSome(), true)
|
@Expect(some.isSome(), true)
|
||||||
if (let Some(v) <- some) {
|
if (let Some(v) <- some) {
|
||||||
@Expect(v, "hi")
|
@Expect(v, "hi")
|
||||||
@@ -319,10 +348,10 @@ public class JsonSerializerTests {
|
|||||||
e._name = "login"
|
e._name = "login"
|
||||||
e._time = DateTime.parse("2026-08-21 02:27:48", "yyyy-MM-dd HH:mm:ss")
|
e._time = DateTime.parse("2026-08-21 02:27:48", "yyyy-MM-dd HH:mm:ss")
|
||||||
e._optTime = Some(DateTime.parse("2026-01-02 03:04:05", "yyyy-MM-dd HH:mm:ss"))
|
e._optTime = Some(DateTime.parse("2026-01-02 03:04:05", "yyyy-MM-dd HH:mm:ss"))
|
||||||
let json = JsonSerializer.Serialize(e)
|
let json = JsonSerializer.serialize(e)
|
||||||
@Expect(json.contains("\"_time\": \"2026-08-21 02:27:48\""), true)
|
@Expect(json.contains("\"_time\": \"2026-08-21 02:27:48\""), true)
|
||||||
@Expect(json.contains("\"_optTime\": \"2026-01-02 03:04:05\""), true)
|
@Expect(json.contains("\"_optTime\": \"2026-01-02 03:04:05\""), true)
|
||||||
let back = JsonSerializer.Deserialize<Event>(json)
|
let back = JsonSerializer.deserialize<Event>(json)
|
||||||
@Expect(back._time.format("yyyy-MM-dd HH:mm:ss"), "2026-08-21 02:27:48")
|
@Expect(back._time.format("yyyy-MM-dd HH:mm:ss"), "2026-08-21 02:27:48")
|
||||||
let optVal: ?DateTime = back._optTime
|
let optVal: ?DateTime = back._optTime
|
||||||
@Expect(optVal.isSome(), true)
|
@Expect(optVal.isSome(), true)
|
||||||
@@ -330,14 +359,14 @@ public class JsonSerializerTests {
|
|||||||
@Expect(dt.format("yyyy-MM-dd HH:mm:ss"), "2026-01-02 03:04:05")
|
@Expect(dt.format("yyyy-MM-dd HH:mm:ss"), "2026-01-02 03:04:05")
|
||||||
}
|
}
|
||||||
// 顶层 DateTime 直接序列化
|
// 顶层 DateTime 直接序列化
|
||||||
@Expect(JsonSerializer.Serialize(back._time), "\"2026-08-21 02:27:48\"")
|
@Expect(JsonSerializer.serialize(back._time), "\"2026-08-21 02:27:48\"")
|
||||||
// Option<DateTime> None → null
|
// Option<DateTime> None → null
|
||||||
let e2 = Event()
|
let e2 = Event()
|
||||||
e2._name = "n"
|
e2._name = "n"
|
||||||
e2._time = DateTime.parse("2026-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss")
|
e2._time = DateTime.parse("2026-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss")
|
||||||
let json2 = JsonSerializer.Serialize(e2)
|
let json2 = JsonSerializer.serialize(e2)
|
||||||
@Expect(json2.contains("\"_optTime\": null"), true)
|
@Expect(json2.contains("\"_optTime\": null"), true)
|
||||||
let back2 = JsonSerializer.Deserialize<Event>(json2)
|
let back2 = JsonSerializer.deserialize<Event>(json2)
|
||||||
@Expect(back2._optTime.isNone(), true)
|
@Expect(back2._optTime.isNone(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,16 +374,16 @@ public class JsonSerializerTests {
|
|||||||
@TestCase
|
@TestCase
|
||||||
public func looseConversion(): Unit {
|
public func looseConversion(): Unit {
|
||||||
// 数字 → String 字段
|
// 数字 → String 字段
|
||||||
let s: String = JsonSerializer.Deserialize<String>("42")
|
let s: String = JsonSerializer.deserialize<String>("42")
|
||||||
@Expect(s, "42")
|
@Expect(s, "42")
|
||||||
// 字符串 → Int64 字段
|
// 字符串 → Int64 字段
|
||||||
let i: Int64 = JsonSerializer.Deserialize<Int64>("\"42\"")
|
let i: Int64 = JsonSerializer.deserialize<Int64>("\"42\"")
|
||||||
@Expect(i, Int64(42))
|
@Expect(i, Int64(42))
|
||||||
// 浮点 → Int64(截断)
|
// 浮点 → Int64(截断)
|
||||||
let j: Int64 = JsonSerializer.Deserialize<Int64>("3.9")
|
let j: Int64 = JsonSerializer.deserialize<Int64>("3.9")
|
||||||
@Expect(j, Int64(3))
|
@Expect(j, Int64(3))
|
||||||
// 字符串 → Bool
|
// 字符串 → Bool
|
||||||
let b: Bool = JsonSerializer.Deserialize<Bool>("\"true\"")
|
let b: Bool = JsonSerializer.deserialize<Bool>("\"true\"")
|
||||||
@Expect(b, true)
|
@Expect(b, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +394,7 @@ public class JsonSerializerTests {
|
|||||||
opt.maxDepth = 0
|
opt.maxDepth = 0
|
||||||
let u = User()
|
let u = User()
|
||||||
let threw = try {
|
let threw = try {
|
||||||
JsonSerializer.Serialize(u, opt)
|
JsonSerializer.serialize(u, opt)
|
||||||
false
|
false
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
true
|
true
|
||||||
@@ -386,16 +415,75 @@ 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")
|
||||||
// @SerializerIgnore 父类字段不导出
|
// @Ignore 父类字段不导出
|
||||||
@Expect(m.contains("_temp"), false)
|
@Expect(m.contains("_temp"), false)
|
||||||
// 宏生成的导入方法
|
// 宏生成的导入方法(默认必填:三个 var 字段键都须存在,@Ignore 的 _temp 除外)
|
||||||
var json = HashMap<String, Any>()
|
var json = HashMap<String, Any>()
|
||||||
json["_id"] = "m-2"
|
json["_id"] = "m-2"
|
||||||
|
json["_createdAt"] = "2025-03-03"
|
||||||
json["parent_alias"] = "alias-m2"
|
json["parent_alias"] = "alias-m2"
|
||||||
BaseUser.importJsonFields(u, json)
|
BaseUser.importJsonFields(u, json)
|
||||||
@Expect(u._id, "m-2")
|
@Expect(u._id, "m-2")
|
||||||
@Expect(u._parentAlias, "alias-m2")
|
@Expect(u._parentAlias, "alias-m2")
|
||||||
@Expect(u._createdAt, "2025-02-02") // 未传的键保持原值
|
@Expect(u._createdAt, "2025-03-03") // 键存在则回填
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 默认必填:未标注字段键缺失 → 抛异常;@NotRequired 字段键缺失 → 允许(保持默认值)
|
||||||
|
@TestCase
|
||||||
|
public func requiredField(): Unit {
|
||||||
|
// 全部键存在:正常反序列化
|
||||||
|
let ok = JsonSerializer.deserialize<RequiredUser>("{\"_id\": \"r-1\", \"_name\": \"tom\"}")
|
||||||
|
@Expect(ok._id, "r-1")
|
||||||
|
@Expect(ok._name, "tom")
|
||||||
|
// 默认必填字段 _id 缺失 → 抛异常(即便 @NotRequired 的 _name 存在)
|
||||||
|
let threw = try {
|
||||||
|
JsonSerializer.deserialize<RequiredUser>("{\"_name\": \"tom\"}")
|
||||||
|
false
|
||||||
|
} catch (_: Exception) {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
@Expect(threw, true)
|
||||||
|
// @NotRequired 字段 _name 缺失 → 允许,保持默认值
|
||||||
|
let okMissing = JsonSerializer.deserialize<RequiredUser>("{\"_id\": \"r-2\"}")
|
||||||
|
@Expect(okMissing._id, "r-2")
|
||||||
|
@Expect(okMissing._name, "")
|
||||||
|
// 默认必填字段键存在但为 null:视为「存在」,不抛异常,保持默认值
|
||||||
|
let nullId = try {
|
||||||
|
let u = JsonSerializer.deserialize<RequiredUser>("{\"_id\": null, \"_name\": \"tom\"}")
|
||||||
|
u._id
|
||||||
|
} catch (_: Exception) {
|
||||||
|
"<threw>"
|
||||||
|
}
|
||||||
|
@Expect(nullId, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 默认必填 + @SerializerParent:父类默认必填字段缺失 → 上抛;@NotRequired 父类字段缺失 → 允许
|
||||||
|
@TestCase
|
||||||
|
public func requiredParentField(): Unit {
|
||||||
|
// 全部键存在:正常反序列化
|
||||||
|
let ok = try {
|
||||||
|
let c = JsonSerializer.deserialize<ReqChild>("{\"_rid\": \"r-1\", \"_own\": \"x\"}")
|
||||||
|
c._rid
|
||||||
|
} catch (_: Exception) {
|
||||||
|
"<threw>"
|
||||||
|
}
|
||||||
|
@Expect(ok, "r-1")
|
||||||
|
// 父类默认必填字段 _rid 缺失 → importJsonFields 抛"缺少必填字段"并上抛
|
||||||
|
let threw = try {
|
||||||
|
JsonSerializer.deserialize<ReqChild>("{\"_extra\": \"e\", \"_own\": \"x\"}")
|
||||||
|
false
|
||||||
|
} catch (_: Exception) {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
@Expect(threw, true)
|
||||||
|
// 父类 @NotRequired 字段 _extra 缺失 → 允许;但子类默认必填字段 _own 须存在
|
||||||
|
let okMissing = try {
|
||||||
|
let c = JsonSerializer.deserialize<ReqChild>("{\"_rid\": \"r-2\", \"_own\": \"x\"}")
|
||||||
|
c._extra
|
||||||
|
} catch (_: Exception) {
|
||||||
|
"<threw>"
|
||||||
|
}
|
||||||
|
@Expect(okMissing, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 多层继承:爷标宏、中间层不标宏 → 爷字段导出,中间层字段不导出
|
/// 多层继承:爷标宏、中间层不标宏 → 爷字段导出,中间层字段不导出
|
||||||
@@ -406,12 +494,12 @@ public class JsonSerializerTests {
|
|||||||
leaf._mp = "mp-v"
|
leaf._mp = "mp-v"
|
||||||
leaf._lc = "lc-v"
|
leaf._lc = "lc-v"
|
||||||
|
|
||||||
let json = JsonSerializer.Serialize(leaf)
|
let json = JsonSerializer.serialize(leaf)
|
||||||
@Expect(json.contains("\"_gp\": \"gp-v\""), true) // 爷字段(宏)导出
|
@Expect(json.contains("\"_gp\": \"gp-v\""), true) // 爷字段(宏)导出
|
||||||
@Expect(json.contains("_mp"), false) // 中间层无宏不导出
|
@Expect(json.contains("_mp"), false) // 中间层无宏不导出
|
||||||
@Expect(json.contains("\"_lc\": \"lc-v\""), true) // 子字段(反射)导出
|
@Expect(json.contains("\"_lc\": \"lc-v\""), true) // 子字段(反射)导出
|
||||||
|
|
||||||
let back = JsonSerializer.Deserialize<LeafChild>(json)
|
let back = JsonSerializer.deserialize<LeafChild>(json)
|
||||||
@Expect(back._gp, "gp-v")
|
@Expect(back._gp, "gp-v")
|
||||||
@Expect(back._lc, "lc-v")
|
@Expect(back._lc, "lc-v")
|
||||||
@Expect(back._mp, "") // 中间层字段保持默认值
|
@Expect(back._mp, "") // 中间层字段保持默认值
|
||||||
@@ -422,7 +510,7 @@ public class JsonSerializerTests {
|
|||||||
public func parentAutoOpen(): Unit {
|
public func parentAutoOpen(): Unit {
|
||||||
let leaf = LeafChild()
|
let leaf = LeafChild()
|
||||||
leaf._gp = "auto"
|
leaf._gp = "auto"
|
||||||
let json = JsonSerializer.Serialize(leaf)
|
let json = JsonSerializer.serialize(leaf)
|
||||||
@Expect(json.contains("\"_gp\": \"auto\""), true)
|
@Expect(json.contains("\"_gp\": \"auto\""), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,12 +522,12 @@ public class JsonSerializerTests {
|
|||||||
leaf._ma = "ma-v"
|
leaf._ma = "ma-v"
|
||||||
leaf._la = "la-v"
|
leaf._la = "la-v"
|
||||||
|
|
||||||
let json = JsonSerializer.Serialize(leaf)
|
let json = JsonSerializer.serialize(leaf)
|
||||||
@Expect(json.contains("\"_ga\": \"ga-v\""), true) // 爷字段(宏)导出
|
@Expect(json.contains("\"_ga\": \"ga-v\""), true) // 爷字段(宏)导出
|
||||||
@Expect(json.contains("\"_ma\": \"ma-v\""), true) // 父字段(宏)导出
|
@Expect(json.contains("\"_ma\": \"ma-v\""), true) // 父字段(宏)导出
|
||||||
@Expect(json.contains("\"_la\": \"la-v\""), true) // 子字段(反射)导出
|
@Expect(json.contains("\"_la\": \"la-v\""), true) // 子字段(反射)导出
|
||||||
|
|
||||||
let back = JsonSerializer.Deserialize<LeafAll>(json)
|
let back = JsonSerializer.deserialize<LeafAll>(json)
|
||||||
@Expect(back._ga, "ga-v")
|
@Expect(back._ga, "ga-v")
|
||||||
@Expect(back._ma, "ma-v")
|
@Expect(back._ma, "ma-v")
|
||||||
@Expect(back._la, "la-v")
|
@Expect(back._la, "la-v")
|
||||||
@@ -452,7 +540,7 @@ public class JsonSerializerTests {
|
|||||||
leaf._ga = "G"
|
leaf._ga = "G"
|
||||||
leaf._ma = "M"
|
leaf._ma = "M"
|
||||||
leaf._la = "L"
|
leaf._la = "L"
|
||||||
let json = JsonSerializer.Serialize(leaf)
|
let json = JsonSerializer.serialize(leaf)
|
||||||
|
|
||||||
// 三个字段值互不串
|
// 三个字段值互不串
|
||||||
@Expect(json.contains("\"_ga\": \"G\""), true)
|
@Expect(json.contains("\"_ga\": \"G\""), true)
|
||||||
@@ -460,7 +548,7 @@ public class JsonSerializerTests {
|
|||||||
@Expect(json.contains("\"_la\": \"L\""), true)
|
@Expect(json.contains("\"_la\": \"L\""), true)
|
||||||
|
|
||||||
// 反序列化后互不覆盖
|
// 反序列化后互不覆盖
|
||||||
let back = JsonSerializer.Deserialize<LeafAll>(json)
|
let back = JsonSerializer.deserialize<LeafAll>(json)
|
||||||
@Expect(back._ga, "G")
|
@Expect(back._ga, "G")
|
||||||
@Expect(back._ma, "M")
|
@Expect(back._ma, "M")
|
||||||
@Expect(back._la, "L")
|
@Expect(back._la, "L")
|
||||||
|
|||||||
Reference in New Issue
Block a user