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