refactor: 注解与宏改名——Serializer 前缀统一命名
- @JsonPropertyName → @SerializerPropertyName - @JsonIgnore → @SerializerIgnore - @JsonParent → @SerializerParent(宏文件 JsonParentMacro.cj → SerializerParentMacro.cj) - 同步更新:JsonAnnotations、ReflectionCache、宏内部注解判断、 测试、方案文档、serialization_test 独立项目 - 16 用例全绿
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* simapi_serialization 根包锚点(cjpm 要求 src 根目录至少有一个 .cj 文件,否则不扫描子目录)。
|
||||
* 全部实现位于子包 simapi_serialization.json(对应 .NET System.Text.Json)。
|
||||
* 通过 public import 重导出:`import simapi_serialization.*` 仍可直接使用
|
||||
* JsonSerializer / JsonOption / @JsonPropertyName / @JsonIgnore 等。
|
||||
* JsonSerializer / JsonOption / @SerializerPropertyName / @SerializerIgnore 等。
|
||||
*/
|
||||
|
||||
package simapi_serialization
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 对齐 .NET System.Text.Json.Serialization 特性:
|
||||
* - [JsonPropertyName("xxx")] → @JsonPropertyName["xxx"] 指定字段的 JSON 名称
|
||||
* - [JsonIgnore] → @JsonIgnore 序列化/反序列化时忽略该字段
|
||||
* - [SerializerPropertyName("xxx")] → @SerializerPropertyName["xxx"] 指定字段的 JSON 名称
|
||||
* - [SerializerIgnore] → @SerializerIgnore 序列化/反序列化时忽略该字段
|
||||
*
|
||||
* 设计决策:本库序列化/反序列化只处理「var 成员变量」(std.reflect 的 instanceVariables),
|
||||
* 不处理 prop 属性(instanceProperties)。因此注解 target 仅限 MemberVariable:
|
||||
@@ -15,11 +15,11 @@
|
||||
package simapi_serialization.json
|
||||
|
||||
/**
|
||||
* 指定字段的 JSON 名称(对齐 .NET JsonPropertyNameAttribute)。
|
||||
* 用法:@JsonPropertyName["user_name"] public var _username: String = ""
|
||||
* 指定字段的 JSON 名称(对齐 .NET SerializerPropertyNameAttribute)。
|
||||
* 用法:@SerializerPropertyName["user_name"] public var _username: String = ""
|
||||
*/
|
||||
@Annotation[target: [MemberVariable]]
|
||||
public class JsonPropertyName {
|
||||
public class SerializerPropertyName {
|
||||
public let name: String
|
||||
|
||||
public const init(name: String) {
|
||||
@@ -28,10 +28,10 @@ public class JsonPropertyName {
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记字段不做 JSON 处理(对齐 .NET JsonIgnoreAttribute)。
|
||||
* 用法:@JsonIgnore public var _temp: String = ""
|
||||
* 标记字段不做 JSON 处理(对齐 .NET SerializerIgnoreAttribute)。
|
||||
* 用法:@SerializerIgnore public var _temp: String = ""
|
||||
*/
|
||||
@Annotation[target: [MemberVariable]]
|
||||
public class JsonIgnore {
|
||||
public class SerializerIgnore {
|
||||
public const init() {}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public class JsonReader {
|
||||
case None => ()
|
||||
}
|
||||
}
|
||||
// 父类字段(@JsonParent 宏生成的静态导入方法,沿继承链逐层回填)
|
||||
// 父类字段(@SerializerParent 宏生成的静态导入方法,沿继承链逐层回填)
|
||||
readParentFields(value, ct, instance, options)
|
||||
instance
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* JsonSerializer 单元测试(cjpm test)。
|
||||
* 覆盖:基础类型 / 对象字段输出 / 注解(JsonPropertyName、JsonIgnore)/ Option /
|
||||
* 覆盖:基础类型 / 对象字段输出 / 注解(SerializerPropertyName、SerializerIgnore)/ Option /
|
||||
* 集合(Array、ArrayList、HashSet)/ HashMap / 枚举 / 命名策略 /
|
||||
* 继承(平台限制)/ maxDepth / 宽松类型转换。
|
||||
*
|
||||
@@ -27,15 +27,15 @@ class Address {
|
||||
public var zip: String = "" // 无下划线 → 原样输出 zip
|
||||
}
|
||||
|
||||
// 继承测试:父类标注 @JsonParent 宏 → 自动补 open + 生成导出/导入静态方法,
|
||||
// 继承测试:父类标注 @SerializerParent 宏 → 自动补 open + 生成导出/导入静态方法,
|
||||
// 父类字段(_id/_createdAt)经宏的静态类型访问绕开反射声明类校验
|
||||
@JsonParent
|
||||
@SerializerParent
|
||||
open class BaseUser {
|
||||
public var _id: String = ""
|
||||
public var _createdAt: String = ""
|
||||
@JsonIgnore
|
||||
@SerializerIgnore
|
||||
public var _temp: String = ""
|
||||
@JsonPropertyName["parent_alias"]
|
||||
@SerializerPropertyName["parent_alias"]
|
||||
public var _parentAlias: String = ""
|
||||
}
|
||||
|
||||
@@ -49,14 +49,14 @@ class User <: BaseUser {
|
||||
public var remark: ?String = None // 无下划线(Option=None → null)
|
||||
public var _tags: Array<String> = [] // 下划线字段(集合)
|
||||
public var addr: Address = Address() // 无下划线(嵌套对象)
|
||||
@JsonPropertyName["user_alias"]
|
||||
@SerializerPropertyName["user_alias"]
|
||||
public var _alias: String = "" // 下划线 + 注解覆盖
|
||||
@JsonIgnore
|
||||
@SerializerIgnore
|
||||
public var temp: String = "secret" // 无下划线 + 忽略
|
||||
}
|
||||
|
||||
// 多层继承:爷(标宏)→ 父(不标宏)→ 子
|
||||
@JsonParent
|
||||
@SerializerParent
|
||||
open class GrandParent {
|
||||
public var _gp: String = ""
|
||||
}
|
||||
@@ -129,10 +129,10 @@ public class JsonSerializerTests {
|
||||
// 无下划线字段原样输出
|
||||
@Expect(json.contains("\"score\""), true)
|
||||
@Expect(json.contains("\"active\": true"), true)
|
||||
// @JsonPropertyName 覆盖字段名
|
||||
// @SerializerPropertyName 覆盖字段名
|
||||
@Expect(json.contains("\"user_alias\": \"xiaoming\""), true)
|
||||
@Expect(json.contains("\"_alias\""), false)
|
||||
// @JsonIgnore 字段不输出
|
||||
// @SerializerIgnore 字段不输出
|
||||
@Expect(json.contains("temp"), false)
|
||||
@Expect(json.contains("\"secret\""), false)
|
||||
// Option:Some→值,None→null
|
||||
@@ -143,13 +143,13 @@ public class JsonSerializerTests {
|
||||
// 嵌套对象
|
||||
@Expect(json.contains("\"_city\": \"beijing\""), true)
|
||||
@Expect(json.contains("\"zip\": \"100000\""), true)
|
||||
// 父类字段(@JsonParent 宏):_id/_createdAt 输出,@JsonIgnore 父类字段不输出
|
||||
// 父类字段(@SerializerParent 宏):_id/_createdAt 输出,@SerializerIgnore 父类字段不输出
|
||||
@Expect(json.contains("\"_id\": \"u-1\""), true)
|
||||
@Expect(json.contains("\"_createdAt\": \"2025-01-01\""), true)
|
||||
// 父类 @JsonPropertyName["parent_alias"] 生效
|
||||
// 父类 @SerializerPropertyName["parent_alias"] 生效
|
||||
@Expect(json.contains("\"parent_alias\": \"parent-xiaoming\""), true)
|
||||
@Expect(json.contains("_parentAlias"), false)
|
||||
// 父类 @JsonIgnore _temp 不输出
|
||||
// 父类 @SerializerIgnore _temp 不输出
|
||||
@Expect(json.contains("secret-parent"), false)
|
||||
}
|
||||
|
||||
@@ -165,8 +165,8 @@ public class JsonSerializerTests {
|
||||
@Expect(u2.addr._city, "beijing")
|
||||
@Expect(u2.addr.zip, "100000")
|
||||
@Expect(u2._alias, "xiaoming")
|
||||
@Expect(u2.temp, "secret") // @JsonIgnore:反序列化不改动默认值
|
||||
// 父类字段往返(@JsonParent 宏)
|
||||
@Expect(u2.temp, "secret") // @SerializerIgnore:反序列化不改动默认值
|
||||
// 父类字段往返(@SerializerParent 宏)
|
||||
@Expect(u2._id, "u-1")
|
||||
@Expect(u2._createdAt, "2025-01-01")
|
||||
@Expect(u2._parentAlias, "parent-xiaoming")
|
||||
@@ -322,7 +322,7 @@ public class JsonSerializerTests {
|
||||
@Expect(threw, true)
|
||||
}
|
||||
|
||||
/// 父类字段序列化/反序列化(@JsonParent 宏):已并入 objectSerialize/objectRoundTrip,
|
||||
/// 父类字段序列化/反序列化(@SerializerParent 宏):已并入 objectSerialize/objectRoundTrip,
|
||||
/// 此处验证宏生成的静态方法可直接调用
|
||||
@TestCase
|
||||
public func parentMacroMethods() {
|
||||
@@ -335,7 +335,7 @@ public class JsonSerializerTests {
|
||||
@Expect(toStr(m["_id"]), "m-1")
|
||||
@Expect(toStr(m["_createdAt"]), "2025-02-02")
|
||||
@Expect(toStr(m["parent_alias"]), "alias-m")
|
||||
// @JsonIgnore 父类字段不导出
|
||||
// @SerializerIgnore 父类字段不导出
|
||||
@Expect(m.contains("_temp"), false)
|
||||
// 宏生成的导入方法
|
||||
var json = HashMap<String, Any>()
|
||||
@@ -366,7 +366,7 @@ public class JsonSerializerTests {
|
||||
@Expect(back._mp, "") // 中间层字段保持默认值
|
||||
}
|
||||
|
||||
/// 宏自动补 open:GrandParent 未显式写 open(@JsonParent 自动补),继承链可正常编译
|
||||
/// 宏自动补 open:GrandParent 未显式写 open(@SerializerParent 自动补),继承链可正常编译
|
||||
@TestCase
|
||||
public func parentAutoOpen() {
|
||||
let leaf = LeafChild()
|
||||
|
||||
@@ -106,14 +106,14 @@ public class JsonWriter {
|
||||
// 1) 子类自身字段(反射)
|
||||
let fields = ReflectionCache.getFields(ct, options)
|
||||
for (f in fields) {
|
||||
// 只要不是 @JsonIgnore 的字段一律输出;None 字段输出 null
|
||||
// 只要不是 @SerializerIgnore 的字段一律输出;None 字段输出 null
|
||||
if (f.ignore) {
|
||||
continue
|
||||
}
|
||||
let raw = f.variable.getOrThrow().getValue(value)
|
||||
obj.put(f.jsonName, writeValue(raw, options, depth + 1))
|
||||
}
|
||||
// 2) 父类字段(@JsonParent 宏生成的静态导出方法,沿继承链逐层调用)
|
||||
// 2) 父类字段(@SerializerParent 宏生成的静态导出方法,沿继承链逐层调用)
|
||||
writeParentFields(value, ct, obj, options, depth)
|
||||
obj
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* ReflectionCache:字段元数据(含继承链、命名策略、@JsonPropertyName/@JsonIgnore)与缓存。
|
||||
* ReflectionCache:字段元数据(含继承链、命名策略、@SerializerPropertyName/@SerializerIgnore)与缓存。
|
||||
*
|
||||
* 已知限制(平台级):
|
||||
* - InstanceVariableInfo.getValue/setValue 有「声明类严格类型校验」,父类字段无法用子类实例读写,
|
||||
@@ -18,9 +18,9 @@ import std.reflect.*
|
||||
public class FieldMetadata {
|
||||
/// 反射字段名(如 _name)
|
||||
public var name: String = ""
|
||||
/// JSON 名称(@JsonPropertyName 优先,否则按命名策略)
|
||||
/// JSON 名称(@SerializerPropertyName 优先,否则按命名策略)
|
||||
public var jsonName: String = ""
|
||||
/// 是否忽略(@JsonIgnore)
|
||||
/// 是否忽略(@SerializerIgnore)
|
||||
public var ignore: Bool = false
|
||||
/// 字段类型
|
||||
public var typeInfo: ?TypeInfo = None
|
||||
@@ -40,20 +40,20 @@ public class ReflectionCache {
|
||||
|
||||
private static let _cache = HashMap<String, ArrayList<FieldMetadata>>()
|
||||
|
||||
/// 类型 → 是否有 @JsonParent 宏生成的 exportJsonFields(Bool)
|
||||
/// 类型 → 是否有 @SerializerParent 宏生成的 exportJsonFields(Bool)
|
||||
private static let _exportCache = HashMap<String, Bool>()
|
||||
/// 类型 → 是否有 @JsonParent 宏生成的 importJsonFields(Bool)
|
||||
/// 类型 → 是否有 @SerializerParent 宏生成的 importJsonFields(Bool)
|
||||
private static let _importCache = HashMap<String, Bool>()
|
||||
|
||||
/**
|
||||
* 该类型是否含 @JsonParent 宏生成的导出静态方法(含缓存)。
|
||||
* 该类型是否含 @SerializerParent 宏生成的导出静态方法(含缓存)。
|
||||
*/
|
||||
public static func hasExportFields(typeInfo: ClassTypeInfo): Bool {
|
||||
hasParentMethod(typeInfo, "exportJsonFields", _exportCache)
|
||||
}
|
||||
|
||||
/**
|
||||
* 该类型是否含 @JsonParent 宏生成的导入静态方法(含缓存)。
|
||||
* 该类型是否含 @SerializerParent 宏生成的导入静态方法(含缓存)。
|
||||
*/
|
||||
public static func hasImportFields(typeInfo: ClassTypeInfo): Bool {
|
||||
hasParentMethod(typeInfo, "importJsonFields", _importCache)
|
||||
@@ -96,13 +96,13 @@ public class ReflectionCache {
|
||||
meta.variable = Some(v)
|
||||
meta.typeInfo = Some(v.typeInfo)
|
||||
meta.mutable = v.isMutable()
|
||||
// @JsonIgnore
|
||||
if (v.findAnnotation<JsonIgnore>().isSome()) {
|
||||
// @SerializerIgnore
|
||||
if (v.findAnnotation<SerializerIgnore>().isSome()) {
|
||||
meta.ignore = true
|
||||
}
|
||||
// @JsonPropertyName 优先
|
||||
// @SerializerPropertyName 优先
|
||||
var jsonName = ""
|
||||
if (let Some(jsonProp) <- v.findAnnotation<JsonPropertyName>()) {
|
||||
if (let Some(jsonProp) <- v.findAnnotation<SerializerPropertyName>()) {
|
||||
jsonName = jsonProp.name
|
||||
} else {
|
||||
jsonName = applyNamingPolicy(v.name, options.propertyNamingPolicy)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* @JsonParent:标注在父类上,自动生成父类字段的 JSON 导出/导入静态方法。
|
||||
* @SerializerParent:标注在父类上,自动生成父类字段的 JSON 导出/导入静态方法。
|
||||
*
|
||||
* 背景:Cangjie 反射对实例成员的读写有「声明类严格校验」
|
||||
* (declaringClass != TypeInfo.of(instance)),父类字段无法用子类实例
|
||||
@@ -9,12 +9,12 @@
|
||||
* 调用约定静态方法(见 JsonWriter/JsonReader)。
|
||||
*
|
||||
* 用法:
|
||||
* @JsonParent
|
||||
* @SerializerParent
|
||||
* class BaseUser {
|
||||
* public var _id: String = ""
|
||||
* @JsonPropertyName["userId"]
|
||||
* @SerializerPropertyName["userId"]
|
||||
* public var _userId: String = ""
|
||||
* @JsonIgnore
|
||||
* @SerializerIgnore
|
||||
* public var _temp: String = ""
|
||||
* }
|
||||
*
|
||||
@@ -22,7 +22,7 @@
|
||||
* - 非 open class 自动补 open
|
||||
* - 生成 exportJsonFields(instance: T): HashMap<String, Any>
|
||||
* - 生成 importJsonFields(instance: T, json: HashMap<String, Any>): Unit
|
||||
* - @JsonIgnore 字段跳过;@JsonPropertyName 用注解名做 JSON 键
|
||||
* - @SerializerIgnore 字段跳过;@SerializerPropertyName 用注解名做 JSON 键
|
||||
*/
|
||||
|
||||
macro package simapi_serialization.macros
|
||||
@@ -52,7 +52,7 @@ private class FieldInfo {
|
||||
* @param input 被标注的类声明。
|
||||
* @return 注入静态方法后的类声明。
|
||||
*/
|
||||
public macro JsonParent(input: Tokens): Tokens {
|
||||
public macro SerializerParent(input: Tokens): Tokens {
|
||||
let decl = parseDecl(input)
|
||||
if (let cd: ClassDecl <- decl) {
|
||||
// 1. open 处理:自动补 open(类被标注即表达「要被继承」的意图)
|
||||
@@ -82,10 +82,10 @@ public macro JsonParent(input: Tokens): Tokens {
|
||||
|
||||
return cd.toTokens()
|
||||
}
|
||||
throw ASTException("@JsonParent 只能标注在 class 声明上(不支持 struct/interface/enum)")
|
||||
throw ASTException("@SerializerParent 只能标注在 class 声明上(不支持 struct/interface/enum)")
|
||||
}
|
||||
|
||||
/// 收集类体内的可序列化字段(_ 前缀 + 类型注解;跳过 @JsonIgnore;@JsonPropertyName 用注解名)
|
||||
/// 收集类体内的可序列化字段(_ 前缀 + 类型注解;跳过 @SerializerIgnore;@SerializerPropertyName 用注解名)
|
||||
private func collectFields(cd: ClassDecl): ArrayList<FieldInfo> {
|
||||
var result = ArrayList<FieldInfo>()
|
||||
for (d in cd.body.decls) {
|
||||
@@ -96,13 +96,13 @@ private func collectFields(cd: ClassDecl): ArrayList<FieldInfo> {
|
||||
vd.declType.toTokens().toString(), vd.keyword.kind == TokenKind.VAR))
|
||||
}
|
||||
case md: MacroExpandDecl =>
|
||||
// 带注解的字段:@JsonIgnore 跳过;@JsonPropertyName["x"] 取注解名
|
||||
if (md.identifier.value == "JsonIgnore") {
|
||||
// 带注解的字段:@SerializerIgnore 跳过;@SerializerPropertyName["x"] 取注解名
|
||||
if (md.identifier.value == "SerializerIgnore") {
|
||||
continue
|
||||
}
|
||||
if (let Some(inner) <- unwrapMacroField(md)) {
|
||||
if (isSerializableField(inner)) {
|
||||
let jsonName = if (md.identifier.value == "JsonPropertyName") {
|
||||
let jsonName = if (md.identifier.value == "SerializerPropertyName") {
|
||||
// 属性 tokens 形如 "user_alias"(带引号),去掉两端引号
|
||||
stripQuotes(md.macroAttrs.toString())
|
||||
} else {
|
||||
@@ -144,7 +144,7 @@ private class FieldVisitor <: Visitor {
|
||||
}
|
||||
}
|
||||
|
||||
/// 去掉字符串两端引号(JsonPropertyName 属性 tokens 形如 "user_alias")
|
||||
/// 去掉字符串两端引号(SerializerPropertyName 属性 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]
|
||||
Reference in New Issue
Block a user