feat: 注解重命名(@PropertyName/@Ignore) + 新增 @NotRequired 默认必填语义, API 小写化 serialize/deserialize, 宏支持多注解组合, 版本号 1.2.0

This commit is contained in:
2026-08-25 11:59:13 +08:00
parent 1071d18a62
commit cd1b56fb81
12 changed files with 632 additions and 265 deletions
+59 -20
View File
@@ -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`)
+1 -1
View File
@@ -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
View File
@@ -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 编译失败 |
+11 -3
View File
@@ -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
+21 -6
View File
@@ -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() {}
} }
+30 -22
View File
@@ -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()
} }
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
} }
+88 -13
View File
@@ -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()
} }
+151 -63
View File
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* JsonSerializer 单元测试(cjpm test)。 * JsonSerializer 单元测试(cjpm test)。
* 覆盖:基础类型 / 对象字段输出 / 注解(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")