Files
serialization-cj/src/json/JsonSerializer_test.cj
T
xrain affadac6c2 feat: 实现 @JsonParent 宏——父类字段序列化/反序列化支持
- 新增 src/macros/JsonParentMacro.cj(macro package simapi_serialization.macros):
  标注父类自动补 open,收集 public var 字段(跳过 @JsonIgnore,
  @JsonPropertyName 用注解名),字符串拼接生成静态方法:
    exportJsonFields(instance: T): HashMap<String, Any>
    importJsonFields(instance: T, json: HashMap<String, Any>): Unit
- JsonWriter.writeParentFields:沿 superClass 链调用 exportJsonFields 合并父类字段
- JsonReader.readParentFields:沿 superClass 链调用 importJsonFields 回填父类字段
- ReflectionCache 增加 hasExportFields/hasImportFields 缓存(避免每次反射查找)
- 测试:16 用例全绿——新增 parentMacroMethods、multiLevelInheritance、
  parentAutoOpen;更新 objectSerialize/objectRoundTrip 断言父类字段输出
2026-08-17 13:41:16 +08:00

378 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (c) 2025 SimcuTeam. All rights reserved.
* JsonSerializer 单元测试(cjpm test)。
* 覆盖:基础类型 / 对象字段输出 / 注解(JsonPropertyName、JsonIgnore/ Option /
* 集合(Array、ArrayList、HashSet/ HashMap / 枚举 / 命名策略 /
* 继承(平台限制)/ maxDepth / 宽松类型转换。
*
* 运行:cjpm test
*/
package simapi_serialization.json
import std.collection.*
import std.unittest.*
import std.unittest.testmacro.*
import simapi_serialization.macros.*
enum Role {
| Admin
| User
| Guest
}
// 混合测试:_ 前缀字段 与 无下划线字段 同时存在
class Address {
public var _city: String = "" // 下划线字段 → 原样输出 _city
public var zip: String = "" // 无下划线 → 原样输出 zip
}
// 继承测试:父类标注 @JsonParent 宏 → 自动补 open + 生成导出/导入静态方法,
// 父类字段(_id/_createdAt)经宏的静态类型访问绕开反射声明类校验
@JsonParent
open class BaseUser {
public var _id: String = ""
public var _createdAt: String = ""
@JsonIgnore
public var _temp: String = ""
@JsonPropertyName["parent_alias"]
public var _parentAlias: String = ""
}
class User <: BaseUser {
public var _name: String = "" // 下划线字段
public var _age: Int64 = 0 // 下划线字段
public var score: Float64 = 0.0 // 无下划线
public var active: Bool = true // 无下划线
public var _role: Role = Role.User // 下划线字段(枚举)
public var opt: ?String = None // 无下划线(Option=Some
public var remark: ?String = None // 无下划线(Option=None → null
public var _tags: Array<String> = [] // 下划线字段(集合)
public var addr: Address = Address() // 无下划线(嵌套对象)
@JsonPropertyName["user_alias"]
public var _alias: String = "" // 下划线 + 注解覆盖
@JsonIgnore
public var temp: String = "secret" // 无下划线 + 忽略
}
// 多层继承:爷(标宏)→ 父(不标宏)→ 子
@JsonParent
open class GrandParent {
public var _gp: String = ""
}
open class MidParent <: GrandParent {
public var _mp: String = ""
}
class LeafChild <: MidParent {
public var _lc: String = ""
public init() {}
}
func makeUser(): User {
let u = User()
u._id = "u-1"
u._createdAt = "2025-01-01"
u._temp = "secret-parent"
u._parentAlias = "parent-xiaoming"
u._name = "alice"
u._age = 30
u.score = 88.5
u.active = true
u._role = Role.Admin
u.opt = Some("hello")
u._tags = ["a", "b", "c"]
u.addr._city = "beijing"
u.addr.zip = "100000"
u._alias = "xiaoming"
u.temp = "hidden"
u
}
func roleStr(r: Role): String {
match (r) {
case Role.Admin => "Admin"
case Role.User => "User"
case Role.Guest => "Guest"
}
}
func toStr(x: Any): String {
if (let s: String <- x) { return s }
if (let s: ToString <- x) { return s.toString() }
"?"
}
@Test
public class JsonSerializerTests {
/// 基础类型序列化
@TestCase
public func basicTypes() {
@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() {
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)
// @JsonPropertyName 覆盖字段名
@Expect(json.contains("\"user_alias\": \"xiaoming\""), true)
@Expect(json.contains("\"_alias\""), false)
// @JsonIgnore 字段不输出
@Expect(json.contains("temp"), false)
@Expect(json.contains("\"secret\""), false)
// OptionSome→值,None→null
@Expect(json.contains("\"opt\": \"hello\""), true)
@Expect(json.contains("\"remark\": null"), true)
// 枚举 → 名字字符串
@Expect(json.contains("\"_role\": \"Admin\""), true)
// 嵌套对象
@Expect(json.contains("\"_city\": \"beijing\""), true)
@Expect(json.contains("\"zip\": \"100000\""), true)
// 父类字段(@JsonParent 宏):_id/_createdAt 输出,@JsonIgnore 父类字段不输出
@Expect(json.contains("\"_id\": \"u-1\""), true)
@Expect(json.contains("\"_createdAt\": \"2025-01-01\""), true)
// 父类 @JsonPropertyName["parent_alias"] 生效
@Expect(json.contains("\"parent_alias\": \"parent-xiaoming\""), true)
@Expect(json.contains("_parentAlias"), false)
// 父类 @JsonIgnore _temp 不输出
@Expect(json.contains("secret-parent"), false)
}
/// 对象反序列化往返
@TestCase
public func objectRoundTrip() {
let json = JsonSerializer.Serialize(makeUser())
let u2 = JsonSerializer.Deserialize<User>(json)
@Expect(u2._name, "alice")
@Expect(u2._age, Int64(30))
@Expect(u2.active, true)
@Expect(roleStr(u2._role), "Admin")
@Expect(u2.addr._city, "beijing")
@Expect(u2.addr.zip, "100000")
@Expect(u2._alias, "xiaoming")
@Expect(u2.temp, "secret") // @JsonIgnore:反序列化不改动默认值
// 父类字段往返(@JsonParent 宏)
@Expect(u2._id, "u-1")
@Expect(u2._createdAt, "2025-01-01")
@Expect(u2._parentAlias, "parent-xiaoming")
// Option 往返
let optVal: ?String = u2.opt
@Expect(optVal.isSome(), true)
if (let Some(v) <- optVal) {
@Expect(v, "hello")
}
let remarkVal: ?String = u2.remark
@Expect(remarkVal.isNone(), true)
// 集合往返
@Expect(u2._tags.size, Int64(3))
@Expect(u2._tags[0], "a")
@Expect(u2._tags[1], "b")
@Expect(u2._tags[2], "c")
}
/// Array 往返
@TestCase
public func arrayRoundTrip() {
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")
}
/// ArrayList 往返
@TestCase
public func arrayListRoundTrip() {
var list = ArrayList<String>()
list.add("a")
list.add("b")
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")
}
/// HashSet 往返
@TestCase
public func hashSetRoundTrip() {
var set = HashSet<String>()
set.add("x")
set.add("y")
let json = JsonSerializer.Serialize(set)
let back = JsonSerializer.Deserialize<HashSet<String>>(json)
@Expect(back.contains("x"), true)
@Expect(back.contains("y"), true)
}
/// HashMap<String, V> 往返
@TestCase
public func hashMapRoundTrip() {
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)
@Expect(toStr(mBack["k1"]), "v1")
@Expect(toStr(mBack["k2"]), "42")
}
/// 命名策略:NONE / CAMEL / SNAKE 对当前模型(全小写+下划线)输出一致
@TestCase
public func namingPolicies() {
let optNone = JsonOption()
optNone.propertyNamingPolicy = PropertyNamingPolicy.None
let jsonNone = JsonSerializer.Serialize(makeUser(), optNone)
let optCamel = JsonOption()
optCamel.propertyNamingPolicy = PropertyNamingPolicy.CamelCase
let jsonCamel = JsonSerializer.Serialize(makeUser(), optCamel)
let optSnake = JsonOption()
optSnake.propertyNamingPolicy = PropertyNamingPolicy.SnakeCase
let jsonSnake = JsonSerializer.Serialize(makeUser(), optSnake)
// 模型字段全为小写/下划线,三种策略结果应一致
@Expect(jsonNone, jsonCamel)
@Expect(jsonNone, jsonSnake)
}
/// 命名策略转换规则(直接单测)
@TestCase
public func applyNamingPolicy() {
// SnakeCase:大写转 _小写
@Expect(ReflectionCache.applyNamingPolicy("userName", PropertyNamingPolicy.SnakeCase), "user_name")
@Expect(ReflectionCache.applyNamingPolicy("userNameAge", PropertyNamingPolicy.SnakeCase), "user_name_age")
// 下划线不剥除、不双写
@Expect(ReflectionCache.applyNamingPolicy("_name", PropertyNamingPolicy.SnakeCase), "_name")
// CamelCase:仅首字母小写
@Expect(ReflectionCache.applyNamingPolicy("Name", PropertyNamingPolicy.CamelCase), "name")
@Expect(ReflectionCache.applyNamingPolicy("_name", PropertyNamingPolicy.CamelCase), "_name")
// None:原样
@Expect(ReflectionCache.applyNamingPolicy("Name", PropertyNamingPolicy.None), "Name")
}
/// 枚举序列化/反序列化
@TestCase
public func enumSerialize() {
@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))
@Expect(roleStr(back._role), "Guest")
}
/// Option 字段:null → None,值 → Some
@TestCase
public func optionField() {
let none: ?String = JsonSerializer.Deserialize<?String>("null")
@Expect(none.isNone(), true)
let some: ?String = JsonSerializer.Deserialize<?String>("\"hi\"")
@Expect(some.isSome(), true)
if (let Some(v) <- some) {
@Expect(v, "hi")
}
}
/// 宽松类型转换:JSON 数字 ↔ 字符串字段互转
@TestCase
public func looseConversion() {
// 数字 → String 字段
let s: String = JsonSerializer.Deserialize<String>("42")
@Expect(s, "42")
// 字符串 → Int64 字段
let i: Int64 = JsonSerializer.Deserialize<Int64>("\"42\"")
@Expect(i, Int64(42))
// 浮点 → Int64(截断)
let j: Int64 = JsonSerializer.Deserialize<Int64>("3.9")
@Expect(j, Int64(3))
// 字符串 → Bool
let b: Bool = JsonSerializer.Deserialize<Bool>("\"true\"")
@Expect(b, true)
}
/// 深度限制:超过 maxDepth 抛异常
@TestCase
public func maxDepth() {
let opt = JsonOption()
opt.maxDepth = 0
let u = User()
let threw = try {
JsonSerializer.Serialize(u, opt)
false
} catch (_: Exception) {
true
}
@Expect(threw, true)
}
/// 父类字段序列化/反序列化(@JsonParent 宏):已并入 objectSerialize/objectRoundTrip
/// 此处验证宏生成的静态方法可直接调用
@TestCase
public func parentMacroMethods() {
let u = User()
u._id = "m-1"
u._createdAt = "2025-02-02"
u._parentAlias = "alias-m"
// 宏生成的导出方法:子类实例作父类参数
let m = BaseUser.exportJsonFields(u)
@Expect(toStr(m["_id"]), "m-1")
@Expect(toStr(m["_createdAt"]), "2025-02-02")
@Expect(toStr(m["parent_alias"]), "alias-m")
// @JsonIgnore 父类字段不导出
@Expect(m.contains("_temp"), false)
// 宏生成的导入方法
var json = HashMap<String, Any>()
json["_id"] = "m-2"
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") // 未传的键保持原值
}
/// 多层继承:爷标宏、中间层不标宏 → 爷字段导出,中间层字段不导出
@TestCase
public func multiLevelInheritance() {
let leaf = LeafChild()
leaf._gp = "gp-v"
leaf._mp = "mp-v"
leaf._lc = "lc-v"
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)
@Expect(back._gp, "gp-v")
@Expect(back._lc, "lc-v")
@Expect(back._mp, "") // 中间层字段保持默认值
}
/// 宏自动补 openGrandParent 未显式写 open@JsonParent 自动补),继承链可正常编译
@TestCase
public func parentAutoOpen() {
let leaf = LeafChild()
leaf._gp = "auto"
let json = JsonSerializer.Serialize(leaf)
@Expect(json.contains("\"_gp\": \"auto\""), true)
}
}