2026-08-17 10:47:46 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
2026-08-17 13:58:05 +08:00
|
|
|
|
* ReflectionCache:字段元数据(含继承链、命名策略、@SerializerPropertyName/@SerializerIgnore)与缓存。
|
2026-08-17 10:47:46 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 已知限制(平台级):
|
|
|
|
|
|
* - InstanceVariableInfo.getValue/setValue 有「声明类严格类型校验」,父类字段无法用子类实例读写,
|
|
|
|
|
|
* 故 v1 仅枚举运行时类自身声明的 var 字段(父类字段暂不序列化)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-08-17 11:03:01 +08:00
|
|
|
|
package simapi_serialization.json
|
2026-08-17 10:47:46 +08:00
|
|
|
|
|
|
|
|
|
|
import std.collection.*
|
|
|
|
|
|
import std.reflect.*
|
2026-08-17 14:00:47 +08:00
|
|
|
|
import simapi_serialization.*
|
2026-08-17 10:47:46 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 单个字段的序列化元数据。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class FieldMetadata {
|
|
|
|
|
|
/// 反射字段名(如 _name)
|
|
|
|
|
|
public var name: String = ""
|
2026-08-17 13:58:05 +08:00
|
|
|
|
/// JSON 名称(@SerializerPropertyName 优先,否则按命名策略)
|
2026-08-17 10:47:46 +08:00
|
|
|
|
public var jsonName: String = ""
|
2026-08-17 13:58:05 +08:00
|
|
|
|
/// 是否忽略(@SerializerIgnore)
|
2026-08-17 10:47:46 +08:00
|
|
|
|
public var ignore: Bool = false
|
|
|
|
|
|
/// 字段类型
|
|
|
|
|
|
public var typeInfo: ?TypeInfo = None
|
|
|
|
|
|
/// 字段读写句柄
|
|
|
|
|
|
public var variable: ?InstanceVariableInfo = None
|
|
|
|
|
|
/// 是否可变(反序列化只写 var)
|
|
|
|
|
|
public var mutable: Bool = false
|
|
|
|
|
|
|
|
|
|
|
|
public init() {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 字段反射缓存:类 → 字段列表(首次反射,后续复用)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class ReflectionCache {
|
|
|
|
|
|
private init() {}
|
|
|
|
|
|
|
|
|
|
|
|
private static let _cache = HashMap<String, ArrayList<FieldMetadata>>()
|
|
|
|
|
|
|
2026-08-17 13:58:05 +08:00
|
|
|
|
/// 类型 → 是否有 @SerializerParent 宏生成的 exportJsonFields(Bool)
|
2026-08-17 13:41:16 +08:00
|
|
|
|
private static let _exportCache = HashMap<String, Bool>()
|
2026-08-17 13:58:05 +08:00
|
|
|
|
/// 类型 → 是否有 @SerializerParent 宏生成的 importJsonFields(Bool)
|
2026-08-17 13:41:16 +08:00
|
|
|
|
private static let _importCache = HashMap<String, Bool>()
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-17 13:58:05 +08:00
|
|
|
|
* 该类型是否含 @SerializerParent 宏生成的导出静态方法(含缓存)。
|
2026-08-17 13:41:16 +08:00
|
|
|
|
*/
|
|
|
|
|
|
public static func hasExportFields(typeInfo: ClassTypeInfo): Bool {
|
|
|
|
|
|
hasParentMethod(typeInfo, "exportJsonFields", _exportCache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-17 13:58:05 +08:00
|
|
|
|
* 该类型是否含 @SerializerParent 宏生成的导入静态方法(含缓存)。
|
2026-08-17 13:41:16 +08:00
|
|
|
|
*/
|
|
|
|
|
|
public static func hasImportFields(typeInfo: ClassTypeInfo): Bool {
|
|
|
|
|
|
hasParentMethod(typeInfo, "importJsonFields", _importCache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static func hasParentMethod(typeInfo: ClassTypeInfo, name: String, cache: HashMap<String, Bool>): Bool {
|
|
|
|
|
|
let key = typeInfo.qualifiedName
|
|
|
|
|
|
if (let Some(cached) <- cache.get(key)) {
|
|
|
|
|
|
return cached
|
|
|
|
|
|
}
|
|
|
|
|
|
let found = try {
|
|
|
|
|
|
if (name == "exportJsonFields") {
|
|
|
|
|
|
typeInfo.getStaticFunction(name, [typeInfo])
|
|
|
|
|
|
} else {
|
|
|
|
|
|
typeInfo.getStaticFunction(name, [typeInfo, TypeInfo.of<HashMap<String, Any>>()])
|
|
|
|
|
|
}
|
|
|
|
|
|
true
|
|
|
|
|
|
} catch (_: Exception) {
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
cache[key] = found
|
|
|
|
|
|
found
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 10:47:46 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取类型的字段元数据(含缓存)。
|
|
|
|
|
|
* @param typeInfo 目标类类型。
|
|
|
|
|
|
* @param options 序列化选项(命名策略)。
|
|
|
|
|
|
* @return 字段列表(仅该类自身声明的 var 字段)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static func getFields(typeInfo: ClassTypeInfo, options: JsonOption): ArrayList<FieldMetadata> {
|
|
|
|
|
|
let key = typeInfo.qualifiedName
|
|
|
|
|
|
if (let Some(cached) <- _cache.get(key)) {
|
|
|
|
|
|
return cached
|
|
|
|
|
|
}
|
|
|
|
|
|
var fields = ArrayList<FieldMetadata>()
|
|
|
|
|
|
for (v in typeInfo.instanceVariables) {
|
|
|
|
|
|
var meta = FieldMetadata()
|
|
|
|
|
|
meta.name = v.name
|
|
|
|
|
|
meta.variable = Some(v)
|
|
|
|
|
|
meta.typeInfo = Some(v.typeInfo)
|
|
|
|
|
|
meta.mutable = v.isMutable()
|
2026-08-17 13:58:05 +08:00
|
|
|
|
// @SerializerIgnore
|
|
|
|
|
|
if (v.findAnnotation<SerializerIgnore>().isSome()) {
|
2026-08-17 10:47:46 +08:00
|
|
|
|
meta.ignore = true
|
|
|
|
|
|
}
|
2026-08-17 13:58:05 +08:00
|
|
|
|
// @SerializerPropertyName 优先
|
2026-08-17 10:47:46 +08:00
|
|
|
|
var jsonName = ""
|
2026-08-17 13:58:05 +08:00
|
|
|
|
if (let Some(jsonProp) <- v.findAnnotation<SerializerPropertyName>()) {
|
2026-08-17 10:47:46 +08:00
|
|
|
|
jsonName = jsonProp.name
|
|
|
|
|
|
} else {
|
|
|
|
|
|
jsonName = applyNamingPolicy(v.name, options.propertyNamingPolicy)
|
|
|
|
|
|
}
|
|
|
|
|
|
meta.jsonName = jsonName
|
|
|
|
|
|
fields.add(meta)
|
|
|
|
|
|
}
|
|
|
|
|
|
_cache[key] = fields
|
|
|
|
|
|
fields
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 应用属性命名策略。
|
2026-08-17 11:12:22 +08:00
|
|
|
|
* 说明:不剥除字段名前导下划线——`_name` 原样输出 `_name`;
|
|
|
|
|
|
* 策略只影响字母大小写(CamelCase 首字母小写、SnakeCase 大写转下划线)。
|
2026-08-17 10:47:46 +08:00
|
|
|
|
*/
|
|
|
|
|
|
public static func applyNamingPolicy(fieldName: String, policy: PropertyNamingPolicy): String {
|
|
|
|
|
|
match (policy) {
|
2026-08-17 11:12:22 +08:00
|
|
|
|
case PropertyNamingPolicy.None => fieldName
|
|
|
|
|
|
case PropertyNamingPolicy.CamelCase => toCamelCase(fieldName)
|
|
|
|
|
|
case PropertyNamingPolicy.SnakeCase => toSnakeCase(fieldName)
|
2026-08-17 10:47:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static func toCamelCase(s: String): String {
|
|
|
|
|
|
if (s.isEmpty()) {
|
|
|
|
|
|
return s
|
|
|
|
|
|
}
|
|
|
|
|
|
let first = s[0..1].toAsciiLower()
|
|
|
|
|
|
"${first}${s[1..]}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static func toSnakeCase(s: String): String {
|
|
|
|
|
|
if (s.isEmpty()) {
|
|
|
|
|
|
return s
|
|
|
|
|
|
}
|
|
|
|
|
|
var sb = StringBuilder()
|
|
|
|
|
|
var first = true
|
|
|
|
|
|
for (c in s.runes()) {
|
|
|
|
|
|
let ch = UInt32(c)
|
|
|
|
|
|
let isUpper = ch >= 0x41 && ch <= 0x5A
|
|
|
|
|
|
if (isUpper) {
|
|
|
|
|
|
if (!first) {
|
|
|
|
|
|
sb.append("_")
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.append(Rune(ch + 0x20))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sb.append(c)
|
|
|
|
|
|
}
|
|
|
|
|
|
first = false
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.toString()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|