/* * Copyright (c) 2025 SimcuTeam. All rights reserved. * JsonSerializer 单元测试(cjpm test)。 * 覆盖:基础类型 / 对象字段输出 / 注解(SerializerPropertyName、SerializerIgnore)/ 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.common.SerializerPropertyName import simapi_serialization.common.SerializerIgnore import simapi_serialization.common.PropertyNamingPolicy import simapi_serialization.common.ReflectionCache import simapi_serialization.macros.* enum Role { | Admin | User | Guest } // 混合测试:_ 前缀字段 与 无下划线字段 同时存在 class Address { public var _city: String = "" // 下划线字段 → 原样输出 _city public var zip: String = "" // 无下划线 → 原样输出 zip } // 继承测试:父类标注 @SerializerParent 宏 → 自动补 open + 生成导出/导入静态方法, // 父类字段(_id/_createdAt)经宏的静态类型访问绕开反射声明类校验 @SerializerParent open class BaseUser { public var _id: String = "" public var _createdAt: String = "" @SerializerIgnore public var _temp: String = "" @SerializerPropertyName["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 = [] // 下划线字段(集合) public var addr: Address = Address() // 无下划线(嵌套对象) @SerializerPropertyName["user_alias"] public var _alias: String = "" // 下划线 + 注解覆盖 @SerializerIgnore public var temp: String = "secret" // 无下划线 + 忽略 } // 多层继承:爷(标宏)→ 父(不标宏)→ 子 @SerializerParent 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) // @SerializerPropertyName 覆盖字段名 @Expect(json.contains("\"user_alias\": \"xiaoming\""), true) @Expect(json.contains("\"_alias\""), false) // @SerializerIgnore 字段不输出 @Expect(json.contains("temp"), false) @Expect(json.contains("\"secret\""), false) // Option:Some→值,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) // 父类字段(@SerializerParent 宏):_id/_createdAt 输出,@SerializerIgnore 父类字段不输出 @Expect(json.contains("\"_id\": \"u-1\""), true) @Expect(json.contains("\"_createdAt\": \"2025-01-01\""), true) // 父类 @SerializerPropertyName["parent_alias"] 生效 @Expect(json.contains("\"parent_alias\": \"parent-xiaoming\""), true) @Expect(json.contains("_parentAlias"), false) // 父类 @SerializerIgnore _temp 不输出 @Expect(json.contains("secret-parent"), false) } /// 对象反序列化往返 @TestCase public func objectRoundTrip() { let json = JsonSerializer.Serialize(makeUser()) let u2 = JsonSerializer.Deserialize(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") // @SerializerIgnore:反序列化不改动默认值 // 父类字段往返(@SerializerParent 宏) @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>(json) @Expect(arrBack.size, Int64(3)) @Expect(arrBack[0], "x") @Expect(arrBack[2], "z") } /// ArrayList 往返 @TestCase public func arrayListRoundTrip() { var list = ArrayList() list.add("a") list.add("b") let json = JsonSerializer.Serialize(list) let back = JsonSerializer.Deserialize>(json) @Expect(back.size, Int64(2)) @Expect(back[0], "a") @Expect(back[1], "b") } /// HashSet 往返 @TestCase public func hashSetRoundTrip() { var set = HashSet() set.add("x") set.add("y") let json = JsonSerializer.Serialize(set) let back = JsonSerializer.Deserialize>(json) @Expect(back.contains("x"), true) @Expect(back.contains("y"), true) } /// HashMap 往返 @TestCase public func hashMapRoundTrip() { let m = HashMap() m["k1"] = "v1" m["k2"] = Int64(42) let json = JsonSerializer.Serialize(m) let mBack = JsonSerializer.Deserialize>(json) @Expect(toStr(mBack["k1"]), "v1") @Expect(toStr(mBack["k2"]), "42") } /// 命名策略:NONE / CAMEL / SNAKE 对当前模型(全小写+下划线)输出一致 @TestCase public func namingPolicies() { let optNone = JsonOption() optNone.propertyNamingPolicy = PropertyNamingPolicy.Keep 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.Keep), "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(JsonSerializer.Serialize(u)) @Expect(roleStr(back._role), "Guest") } /// Option 字段:null → None,值 → Some @TestCase public func optionField() { let none: ?String = JsonSerializer.Deserialize("null") @Expect(none.isNone(), true) let some: ?String = JsonSerializer.Deserialize("\"hi\"") @Expect(some.isSome(), true) if (let Some(v) <- some) { @Expect(v, "hi") } } /// 宽松类型转换:JSON 数字 ↔ 字符串字段互转 @TestCase public func looseConversion() { // 数字 → String 字段 let s: String = JsonSerializer.Deserialize("42") @Expect(s, "42") // 字符串 → Int64 字段 let i: Int64 = JsonSerializer.Deserialize("\"42\"") @Expect(i, Int64(42)) // 浮点 → Int64(截断) let j: Int64 = JsonSerializer.Deserialize("3.9") @Expect(j, Int64(3)) // 字符串 → Bool let b: Bool = JsonSerializer.Deserialize("\"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) } /// 父类字段序列化/反序列化(@SerializerParent 宏):已并入 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") // @SerializerIgnore 父类字段不导出 @Expect(m.contains("_temp"), false) // 宏生成的导入方法 var json = HashMap() 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(json) @Expect(back._gp, "gp-v") @Expect(back._lc, "lc-v") @Expect(back._mp, "") // 中间层字段保持默认值 } /// 宏自动补 open:GrandParent 未显式写 open(@SerializerParent 自动补),继承链可正常编译 @TestCase public func parentAutoOpen() { let leaf = LeafChild() leaf._gp = "auto" let json = JsonSerializer.Serialize(leaf) @Expect(json.contains("\"_gp\": \"auto\""), true) } }