refactor: 新增 common 子包——XML/JSON 共用部分 + 单 import 全量可用
- src/common/(package simapi_serialization.common): Annotations.cj(@SerializerPropertyName/@SerializerIgnore)、 ReflectionCache.cj(字段元数据 + 继承链缓存)、NamingPolicy.cj(PropertyNamingPolicy) - 根锚点 public import common.* + json.*:import simapi_serialization.* 即可用全部 - JsonOption.propertyNamingPolicy 引用 common 枚举;getFields 改收命名策略 - 枚举值 PropertyNamingPolicy.None → Keep(None 与内置 Option.None 构造器同名冲突) - JsonWriter/JsonReader 单类 import common,避免循环依赖 - 测试/独立项目验证:16 用例全绿;serialization_test 单 import 即可运行
This commit is contained in:
+1
-11
@@ -5,17 +5,7 @@
|
||||
|
||||
package simapi_serialization.json
|
||||
|
||||
/**
|
||||
* 属性命名策略(对齐 .NET JsonNamingPolicy)。
|
||||
*/
|
||||
public enum PropertyNamingPolicy {
|
||||
/// 字段名原样(_username → _username)
|
||||
| None
|
||||
/// 去前导下划线 + 首字母小写(_username → username,对齐 simapi/soulsoft 默认)
|
||||
| CamelCase
|
||||
/// 蛇形(userName → user_name,_username → username)
|
||||
| SnakeCase
|
||||
}
|
||||
import simapi_serialization.common.*
|
||||
|
||||
/**
|
||||
* 序列化选项。
|
||||
|
||||
+11
-3
@@ -13,6 +13,8 @@ import std.collection.*
|
||||
import std.convert.*
|
||||
import std.reflect.*
|
||||
import stdx.encoding.json.*
|
||||
import simapi_serialization.common.ReflectionCache
|
||||
import simapi_serialization.common.FieldMetadata
|
||||
|
||||
/**
|
||||
* JsonValue → 对象 读取器。
|
||||
@@ -64,7 +66,8 @@ public class JsonReader {
|
||||
let placeholder = defaultValueFor(innerName)
|
||||
return ctor.apply([placeholder])
|
||||
}
|
||||
return None
|
||||
let noneVal: ?Any = None
|
||||
return noneVal
|
||||
}
|
||||
let innerName = JsonWriter.extractTypeArgs(typeName)[0]
|
||||
let innerVal = readValue(value, TypeInfo.get(innerName), options, depth + 1)
|
||||
@@ -114,7 +117,7 @@ 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)
|
||||
let fields = ReflectionCache.getFields(ct, options.propertyNamingPolicy)
|
||||
let jobj = value.asObject()
|
||||
for (f in fields) {
|
||||
if (f.ignore) {
|
||||
@@ -221,7 +224,7 @@ public class JsonReader {
|
||||
/// JsonValue → 通用 Any(动态结构)
|
||||
private static func jsonToAny(v: JsonValue): Any {
|
||||
match (v.kind()) {
|
||||
case JsNull => None
|
||||
case JsNull => noneAny()
|
||||
case JsBool => v.asBool().getValue()
|
||||
case JsInt => v.asInt().getValue()
|
||||
case JsFloat => v.asFloat().getValue()
|
||||
@@ -241,6 +244,11 @@ public class JsonReader {
|
||||
}
|
||||
}
|
||||
|
||||
private static func noneAny(): Any {
|
||||
let noneVal: ?Any = None
|
||||
noneVal
|
||||
}
|
||||
|
||||
// ===== 宽松类型转换 =====
|
||||
|
||||
private static func readString(v: JsonValue): String {
|
||||
|
||||
@@ -13,9 +13,11 @@ package simapi_serialization.json
|
||||
import std.collection.*
|
||||
import std.unittest.*
|
||||
import std.unittest.testmacro.*
|
||||
import simapi_serialization.*
|
||||
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
|
||||
@@ -237,7 +239,7 @@ public class JsonSerializerTests {
|
||||
@TestCase
|
||||
public func namingPolicies() {
|
||||
let optNone = JsonOption()
|
||||
optNone.propertyNamingPolicy = PropertyNamingPolicy.None
|
||||
optNone.propertyNamingPolicy = PropertyNamingPolicy.Keep
|
||||
let jsonNone = JsonSerializer.Serialize(makeUser(), optNone)
|
||||
|
||||
let optCamel = JsonOption()
|
||||
@@ -265,7 +267,7 @@ public class JsonSerializerTests {
|
||||
@Expect(ReflectionCache.applyNamingPolicy("Name", PropertyNamingPolicy.CamelCase), "name")
|
||||
@Expect(ReflectionCache.applyNamingPolicy("_name", PropertyNamingPolicy.CamelCase), "_name")
|
||||
// None:原样
|
||||
@Expect(ReflectionCache.applyNamingPolicy("Name", PropertyNamingPolicy.None), "Name")
|
||||
@Expect(ReflectionCache.applyNamingPolicy("Name", PropertyNamingPolicy.Keep), "Name")
|
||||
}
|
||||
|
||||
/// 枚举序列化/反序列化
|
||||
|
||||
@@ -11,6 +11,8 @@ package simapi_serialization.json
|
||||
import std.collection.*
|
||||
import std.reflect.*
|
||||
import stdx.encoding.json.*
|
||||
import simapi_serialization.common.ReflectionCache
|
||||
import simapi_serialization.common.FieldMetadata
|
||||
|
||||
/**
|
||||
* 对象 → JsonValue 写入器。
|
||||
@@ -104,7 +106,7 @@ public class JsonWriter {
|
||||
private static func writeObject(value: Any, ct: ClassTypeInfo, options: JsonOption, depth: Int64): JsonValue {
|
||||
let obj = JsonObject()
|
||||
// 1) 子类自身字段(反射)
|
||||
let fields = ReflectionCache.getFields(ct, options)
|
||||
let fields = ReflectionCache.getFields(ct, options.propertyNamingPolicy)
|
||||
for (f in fields) {
|
||||
// 只要不是 @SerializerIgnore 的字段一律输出;None 字段输出 null
|
||||
if (f.ignore) {
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* ReflectionCache:字段元数据(含继承链、命名策略、@SerializerPropertyName/@SerializerIgnore)与缓存。
|
||||
*
|
||||
* 已知限制(平台级):
|
||||
* - InstanceVariableInfo.getValue/setValue 有「声明类严格类型校验」,父类字段无法用子类实例读写,
|
||||
* 故 v1 仅枚举运行时类自身声明的 var 字段(父类字段暂不序列化)。
|
||||
*/
|
||||
|
||||
package simapi_serialization.json
|
||||
|
||||
import std.collection.*
|
||||
import std.reflect.*
|
||||
import simapi_serialization.*
|
||||
|
||||
/**
|
||||
* 单个字段的序列化元数据。
|
||||
*/
|
||||
public class FieldMetadata {
|
||||
/// 反射字段名(如 _name)
|
||||
public var name: String = ""
|
||||
/// JSON 名称(@SerializerPropertyName 优先,否则按命名策略)
|
||||
public var jsonName: String = ""
|
||||
/// 是否忽略(@SerializerIgnore)
|
||||
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>>()
|
||||
|
||||
/// 类型 → 是否有 @SerializerParent 宏生成的 exportJsonFields(Bool)
|
||||
private static let _exportCache = HashMap<String, Bool>()
|
||||
/// 类型 → 是否有 @SerializerParent 宏生成的 importJsonFields(Bool)
|
||||
private static let _importCache = HashMap<String, Bool>()
|
||||
|
||||
/**
|
||||
* 该类型是否含 @SerializerParent 宏生成的导出静态方法(含缓存)。
|
||||
*/
|
||||
public static func hasExportFields(typeInfo: ClassTypeInfo): Bool {
|
||||
hasParentMethod(typeInfo, "exportJsonFields", _exportCache)
|
||||
}
|
||||
|
||||
/**
|
||||
* 该类型是否含 @SerializerParent 宏生成的导入静态方法(含缓存)。
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型的字段元数据(含缓存)。
|
||||
* @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()
|
||||
// @SerializerIgnore
|
||||
if (v.findAnnotation<SerializerIgnore>().isSome()) {
|
||||
meta.ignore = true
|
||||
}
|
||||
// @SerializerPropertyName 优先
|
||||
var jsonName = ""
|
||||
if (let Some(jsonProp) <- v.findAnnotation<SerializerPropertyName>()) {
|
||||
jsonName = jsonProp.name
|
||||
} else {
|
||||
jsonName = applyNamingPolicy(v.name, options.propertyNamingPolicy)
|
||||
}
|
||||
meta.jsonName = jsonName
|
||||
fields.add(meta)
|
||||
}
|
||||
_cache[key] = fields
|
||||
fields
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用属性命名策略。
|
||||
* 说明:不剥除字段名前导下划线——`_name` 原样输出 `_name`;
|
||||
* 策略只影响字母大小写(CamelCase 首字母小写、SnakeCase 大写转下划线)。
|
||||
*/
|
||||
public static func applyNamingPolicy(fieldName: String, policy: PropertyNamingPolicy): String {
|
||||
match (policy) {
|
||||
case PropertyNamingPolicy.None => fieldName
|
||||
case PropertyNamingPolicy.CamelCase => toCamelCase(fieldName)
|
||||
case PropertyNamingPolicy.SnakeCase => toSnakeCase(fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user