commit 8d10c314736250cc6de06ddb9864757b7ca3bda2 Author: xRain Date: Mon Aug 17 10:47:46 2026 +0800 feat: SimApiSerialization 全反射 JSON 序列化库 v1 对齐 .NET System.Text.Json: - JsonSerializer.Serialize(obj, JsonOption) / Deserialize(jsonString, JsonOption) - @JsonPropertyName / @JsonIgnore 特性(运行时注解反射) - JsonOption:PropertyNamingPolicy(None/CamelCase/SnakeCase)、ignoreNull、maxDepth - 全反射实现:基础类型/枚举/Option/Array/ArrayList/HashSet/HashMap/嵌套对象 - 仓颉特性适配: * Option 是泛型枚举 → EnumTypeInfo.destruct/construct 处理 * 集合 get(i) 返回 Option → Some/None 解包迭代 * 集合泛型不变 → 反射方法调用(toArray/get/add/keys)而非 cast * as 返回 Option → ?? 解包;JsonKind 用 is 判断 - 已知限制:反序列化需无参构造+var 字段;父类字段不序列化(平台严格类型校验); 枚举按名字;循环引用用深度上限保护 - 已实测:User 完整回环 + 数组/HashMap 回环 + 特性生效 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec8ef60 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +*.cj.macrocall \ No newline at end of file diff --git a/cjpm.lock b/cjpm.lock new file mode 100644 index 0000000..c42311c --- /dev/null +++ b/cjpm.lock @@ -0,0 +1,3 @@ +version = 0 + +[requires] diff --git a/cjpm.toml b/cjpm.toml new file mode 100644 index 0000000..d168c9c --- /dev/null +++ b/cjpm.toml @@ -0,0 +1,23 @@ +[package] + cjc-version = "1.1.3" + name = "simapi_serialization" + description = "SimApi 全反射 JSON 序列化库(对齐 .NET JsonSerializer.Serialize/Deserialize + JsonPropertyName/JsonIgnore 特性)" + version = "1.0.0" + target-dir = "" + output-type = "static" + +[dependencies] + +[target] + [target.x86_64-w64-mingw32] + compile-option = "-Woff unused --diagnostic-format=noColor -Woff deprecated" + [target.x86_64-w64-mingw32.bin-dependencies] + path-option = [ "${CANGJIE_STDX_PATH}" ] + [target.x86_64-unknown-linux-gnu] + compile-option = "-Woff unused --diagnostic-format=noColor -Woff deprecated" + [target.x86_64-unknown-linux-gnu.bin-dependencies] + path-option = [ "${CANGJIE_STDX_PATH}" ] + [target.aarch64-unknown-linux-gnu] + compile-option = "-Woff unused --diagnostic-format=noColor -Woff deprecated" + [target.aarch64-unknown-linux-gnu.bin-dependencies] + path-option = [ "${CANGJIE_STDX_PATH}" ] diff --git a/src/JsonOption.cj b/src/JsonOption.cj new file mode 100644 index 0000000..a456cc5 --- /dev/null +++ b/src/JsonOption.cj @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 SimcuTeam. All rights reserved. + * JsonOption:序列化选项(对齐 .NET JsonSerializerOptions)。 + */ + +package simapi_serialization + +/** + * 属性命名策略(对齐 .NET JsonNamingPolicy)。 + */ +public enum PropertyNamingPolicy { + /// 字段名原样(_username → _username) + | None + /// 去前导下划线 + 首字母小写(_username → username,对齐 simapi/soulsoft 默认) + | CamelCase + /// 蛇形(userName → user_name,_username → username) + | SnakeCase +} + +/** + * 序列化选项。 + */ +public class JsonOption { + /// 属性命名策略(默认 CamelCase) + public var propertyNamingPolicy: PropertyNamingPolicy = PropertyNamingPolicy.CamelCase + + /// 序列化时是否忽略 null/None 字段(默认 false:None 输出 null) + public var ignoreNull: Bool = false + + /// 枚举序列化为名字(默认 true;false 暂按名字输出,数字模式后续支持) + public var enumAsString: Bool = true + + /// 递归深度上限(默认 64) + public var maxDepth: Int64 = 64 + + public init() {} + + /// 默认选项实例 + public static let instance = JsonOption() +} diff --git a/src/JsonReader.cj b/src/JsonReader.cj new file mode 100644 index 0000000..041b1ad --- /dev/null +++ b/src/JsonReader.cj @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2025 SimcuTeam. All rights reserved. + * JsonReader:JsonValue → 对象(全反射)。 + * + * - 目标类型来自泛型 T(TypeInfo.of()) + * - 反序列化要求:无参构造 + var 字段(let 只读字段跳过) + * - 类型不匹配做宽松转换(JsonInt/JsonFloat/JsonString 互转) + */ + +package simapi_serialization + +import std.collection.* +import std.convert.* +import std.reflect.* +import stdx.encoding.json.* + +/** + * JsonValue → 对象 读取器。 + */ +public class JsonReader { + private init() {} + + public static func read(value: JsonValue, options: JsonOption): T { + let result = readValue(value, TypeInfo.of(), options, 0) + if (let t: T <- result) { + return t + } + throw Exception("JsonSerializer: 反序列化结果类型不匹配") + } + + private static func readValue(value: JsonValue, typeInfo: TypeInfo, options: JsonOption, depth: Int64): Any { + if (depth > options.maxDepth) { + throw Exception("JsonSerializer: 超过最大递归深度 ${options.maxDepth}") + } + let typeName = typeInfo.toString() + + // 基本类型 + match (typeName) { + case "String" => return readString(value) + case "Bool" => return readBool(value) + case "Int64" => return readInt64(value) + case "Int32" => return Int32(readInt64(value)) + case "Int16" => return Int16(readInt64(value)) + case "Int8" => return Int8(readInt64(value)) + case "UInt8" => return UInt8(readInt64(value)) + case "UInt16" => return UInt16(readInt64(value)) + case "UInt32" => return UInt32(readInt64(value)) + case "UInt64" => return UInt64(readInt64(value)) + case "Float64" => return readFloat64(value) + case "Float32" => return Float32(readFloat64(value)) + case "Rune" => return readChar(value) + case _ => () + } + + // Option:null → None;否则读取内层并用枚举构造器包 Some + if (typeName.startsWith("Option<")) { + if (value is JsonNull) { + return None + } + let innerName = JsonWriter.extractTypeArgs(typeName)[0] + let innerVal = readValue(value, TypeInfo.get(innerName), options, depth + 1) + if (let et: EnumTypeInfo <- typeInfo) { + let ctor = et.getConstructor("Some", argsCount: 1) + return ctor.apply([innerVal]) + } + return Some(innerVal) + } + + // 集合 + if (typeName.startsWith("Array<")) { + let elemName = JsonWriter.extractTypeArgs(typeName)[0] + return readArray(value, elemName, options, depth) + } + if (typeName.startsWith("std.collection.ArrayList<")) { + let elemName = JsonWriter.extractTypeArgs(typeName)[0] + return readArrayList(value, elemName, options, depth) + } + if (typeName.startsWith("std.collection.HashSet<")) { + let elemName = JsonWriter.extractTypeArgs(typeName)[0] + return readHashSet(value, elemName, options, depth) + } + if (typeName.startsWith("std.collection.HashMap<")) { + let args = JsonWriter.extractTypeArgs(typeName) + return readHashMap(value, args[0], args[1], options, depth) + } + + // Any / Object → 通用读取 + if (typeName == "Any" || typeName == "Object") { + return jsonToAny(value) + } + + // 枚举:按构造器名创建 + if (let et: EnumTypeInfo <- typeInfo) { + let name = readString(value) + return et.construct(name, []) + } + + // 对象:反射创建 + 赋值 + if (let ct: ClassTypeInfo <- typeInfo) { + return readObject(value, ct, options, depth) + } + + throw Exception("JsonSerializer: 不支持的目标类型 ${typeName}") + } + + private static func readObject(value: JsonValue, ct: ClassTypeInfo, options: JsonOption, depth: Int64): Any { + let instance = ct.construct([]) + let fields = ReflectionCache.getFields(ct, options) + let jobj = value.asObject() + for (f in fields) { + if (f.ignore) { + continue + } + if (!f.mutable) { + continue + } + match (jobj.get(f.jsonName)) { + case Some(jv) => + if (jv is JsonNull) { + continue // null:跳过,保持字段默认值 + } + let fieldVal = readValue(jv, f.typeInfo.getOrThrow(), options, depth + 1) + f.variable.getOrThrow().setValue(instance, fieldVal) + case None => () + } + } + instance + } + + /// Array:经 ArrayList 构造 + add + toArray + private static func readArray(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any { + let listTypeName = "std.collection.ArrayList<${elemName}>" + let listCt = (TypeInfo.get(listTypeName) as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析类型 ${listTypeName}") + let list = listCt.construct([]) + let addFunc = listCt.getInstanceFunction("add", [TypeInfo.get(elemName)]) + let jarr = value.asArray() + for (item in jarr.getItems()) { + let elem = readValue(item, TypeInfo.get(elemName), options, depth + 1) + addFunc.apply(list, [elem]) + } + let toArrayFunc = listCt.getInstanceFunction("toArray", []) + toArrayFunc.apply(list, []) + } + + private static func readArrayList(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any { + let ct = (TypeInfo.get("std.collection.ArrayList<${elemName}>") as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析 ArrayList<${elemName}>") + let list = ct.construct([]) + let addFunc = ct.getInstanceFunction("add", [TypeInfo.get(elemName)]) + let jarr = value.asArray() + for (item in jarr.getItems()) { + let elem = readValue(item, TypeInfo.get(elemName), options, depth + 1) + addFunc.apply(list, [elem]) + } + list + } + + private static func readHashSet(value: JsonValue, elemName: String, options: JsonOption, depth: Int64): Any { + let ct = (TypeInfo.get("std.collection.HashSet<${elemName}>") as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析 HashSet<${elemName}>") + let set = ct.construct([]) + let addFunc = ct.getInstanceFunction("add", [TypeInfo.get(elemName)]) + let jarr = value.asArray() + for (item in jarr.getItems()) { + let elem = readValue(item, TypeInfo.get(elemName), options, depth + 1) + addFunc.apply(set, [elem]) + } + set + } + + private static func readHashMap(value: JsonValue, keyName: String, valName: String, options: JsonOption, + depth: Int64): Any { + let ct = (TypeInfo.get("std.collection.HashMap<${keyName}, ${valName}>") as ClassTypeInfo) ?? throw Exception("JsonSerializer: 无法解析 HashMap<${keyName}, ${valName}>") + let map = ct.construct([]) + let addFunc = ct.getInstanceFunction("add", [TypeInfo.get(keyName), TypeInfo.get(valName)]) + let jobj = value.asObject() + for ((k, jv) in jobj.getFields()) { + let keyVal = readValue(JsonString(k), TypeInfo.get(keyName), options, depth + 1) + let valVal = readValue(jv, TypeInfo.get(valName), options, depth + 1) + addFunc.apply(map, [keyVal, valVal]) + } + map + } + + /// JsonValue → 通用 Any(动态结构) + private static func jsonToAny(v: JsonValue): Any { + match (v.kind()) { + case JsNull => None + case JsBool => v.asBool().getValue() + case JsInt => v.asInt().getValue() + case JsFloat => v.asFloat().getValue() + case JsString => v.asString().getValue() + case JsArray => + var list = ArrayList() + for (item in v.asArray().getItems()) { + list.add(jsonToAny(item)) + } + list.toArray() + case JsObject => + var map = HashMap() + for ((k, val) in v.asObject().getFields()) { + map[k] = jsonToAny(val) + } + map + } + } + + // ===== 宽松类型转换 ===== + + private static func readString(v: JsonValue): String { + if (let s: JsonString <- v) { return s.getValue() } + if (let i: JsonInt <- v) { return "${i.getValue()}" } + if (let f: JsonFloat <- v) { return "${f.getValue()}" } + if (let b: JsonBool <- v) { return "${b.getValue()}" } + throw Exception("JsonSerializer: 无法转换为 String") + } + + private static func readInt64(v: JsonValue): Int64 { + if (let i: JsonInt <- v) { return i.getValue() } + if (let f: JsonFloat <- v) { return Int64(f.getValue()) } + if (let s: JsonString <- v) { return Int64.parse(s.getValue()) } + throw Exception("JsonSerializer: 无法转换为 Int64") + } + + private static func readFloat64(v: JsonValue): Float64 { + if (let f: JsonFloat <- v) { return f.getValue() } + if (let i: JsonInt <- v) { return Float64(i.getValue()) } + if (let s: JsonString <- v) { return Float64.parse(s.getValue()) } + throw Exception("JsonSerializer: 无法转换为 Float64") + } + + private static func readBool(v: JsonValue): Bool { + if (let b: JsonBool <- v) { return b.getValue() } + if (let s: JsonString <- v) { return s.getValue() == "true" } + throw Exception("JsonSerializer: 无法转换为 Bool") + } + + private static func readChar(v: JsonValue): Rune { + let s = readString(v) + for (r in s.runes()) { + return r + } + throw Exception("JsonSerializer: 无法转换为 Char") + } +} diff --git a/src/JsonSerializer.cj b/src/JsonSerializer.cj new file mode 100644 index 0000000..9c6c5ac --- /dev/null +++ b/src/JsonSerializer.cj @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 SimcuTeam. All rights reserved. + * JsonSerializer:公开 API,对齐 .NET System.Text.Json: + * JsonSerializer.Serialize(obj, JsonOption) → JSON 字符串 + * JsonSerializer.Deserialize(jsonString, JsonOption) → T + * + * 全反射实现:任意类(无参构造 + var 字段)无需实现接口、无需宏。 + */ + +package simapi_serialization + +import stdx.encoding.json.* + +/** + * 全反射 JSON 序列化器(对齐 .NET JsonSerializer)。 + */ +public class JsonSerializer { + private init() {} + + /** + * 序列化任意对象为 JSON 字符串。 + * @param obj 任意对象(基础类型/集合/HashMap/枚举/普通类)。 + * @param options 序列化选项(默认 JsonOption.instance)。 + * @return JSON 字符串。 + */ + public static func Serialize(obj: Any, options: JsonOption): String { + JsonWriter.write(obj, options).toJsonString() + } + + /** + * 序列化任意对象为 JSON 字符串(默认选项)。 + */ + public static func Serialize(obj: Any): String { + Serialize(obj, JsonOption.instance) + } + + /** + * 反序列化 JSON 字符串为目标类型。 + * @param jsonString JSON 字符串。 + * @param options 序列化选项(默认 JsonOption.instance)。 + * @return T 实例(要求 T 有无参构造,字段为 var)。 + */ + public static func Deserialize(jsonString: String, options: JsonOption): T { + let value = JsonValue.fromStr(jsonString) + JsonReader.read(value, options) + } + + /** + * 反序列化 JSON 字符串为目标类型(默认选项)。 + */ + public static func Deserialize(jsonString: String): T { + Deserialize(jsonString, JsonOption.instance) + } +} diff --git a/src/JsonWriter.cj b/src/JsonWriter.cj new file mode 100644 index 0000000..5acbdea --- /dev/null +++ b/src/JsonWriter.cj @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2025 SimcuTeam. All rights reserved. + * JsonWriter:对象 → JsonValue(全反射)。 + * + * 集合处理:仓颉泛型是不变的(Array 不能 cast 为 Array), + * 因此通过反射调用 iterator()/hasNext()/next() 与 keys()/get() 迭代元素。 + */ + +package simapi_serialization + +import std.collection.* +import std.reflect.* +import stdx.encoding.json.* + +/** + * 对象 → JsonValue 写入器。 + */ +public class JsonWriter { + private init() {} + + public static func write(obj: Any, options: JsonOption): JsonValue { + writeValue(obj, options, 0) + } + + private static func writeValue(value: Any, options: JsonOption, depth: Int64): JsonValue { + if (depth > options.maxDepth) { + throw Exception("JsonSerializer: 超过最大递归深度 ${options.maxDepth}") + } + // 基本类型(快路径) + if (let s: String <- value) { return JsonString(s) } + if (let b: Bool <- value) { return JsonBool(b) } + if (let i: Int64 <- value) { return JsonInt(i) } + if (let f: Float64 <- value) { return JsonFloat(f) } + if (let i32: Int32 <- value) { return JsonInt(Int64(i32)) } + if (let i16: Int16 <- value) { return JsonInt(Int64(i16)) } + if (let i8: Int8 <- value) { return JsonInt(Int64(i8)) } + if (let u8: UInt8 <- value) { return JsonInt(Int64(u8)) } + if (let u16: UInt16 <- value) { return JsonInt(Int64(u16)) } + if (let u32: UInt32 <- value) { return JsonInt(Int64(u32)) } + if (let u64: UInt64 <- value) { return JsonInt(Int64(u64)) } + if (let f32: Float32 <- value) { return JsonFloat(Float64(f32)) } + if (let c: Rune <- value) { return JsonString("${c}") } + + let typeName = TypeInfo.of(value).toString() + + // Option 值(Some(x) / None):仓颉 Option 是泛型枚举,用 destruct 取关联值 + if (typeName.startsWith("Option<")) { + if (let Some(inner) <- unwrapOptionValue(value)) { + return writeValue(inner, options, depth + 1) + } + return JsonNull() + } + + // 集合:Array / ArrayList / HashSet(统一反射迭代) + if (typeName.startsWith("Array<") || typeName.startsWith("std.collection.ArrayList<") || + typeName.startsWith("std.collection.HashSet<")) { + return writeCollection(value, options, depth) + } + + // HashMap + if (typeName.startsWith("std.collection.HashMap<")) { + return writeHashMap(value, options, depth) + } + + let ti2 = TypeInfo.of(value) + + // 枚举:destruct 取构造器名 + if (let et: EnumTypeInfo <- ti2) { + let (ctor, _) = et.destruct(value) + return JsonString(ctor.name) + } + + // 对象:反射字段 + if (let ct: ClassTypeInfo <- ti2) { + return writeObject(value, ct, options, depth) + } + + throw Exception("JsonSerializer: 不支持的类型 ${typeName}") + } + + private static func writeCollection(value: Any, options: JsonOption, depth: Int64): JsonValue { + let arr = JsonArray() + forEachElement(value) { elem => + arr.add(writeValue(elem, options, depth + 1)) + } + arr + } + + private static func writeHashMap(value: Any, options: JsonOption, depth: Int64): JsonValue { + let obj = JsonObject() + let ti = TypeInfo.of(value) + let typeName = ti.toString() + let args = extractTypeArgs(typeName) + let keyType = if (args.size > 0) { TypeInfo.get(args[0]) } else { TypeInfo.of() } + let getFunc = ti.getInstanceFunction("get", [keyType]) + let keys = ti.getInstanceFunction("keys", []).apply(value, []) + forEachElement(keys) { k => + let v = getFunc.apply(value, [k]) + obj.put(toKeyString(k), writeValue(v, options, depth + 1)) + } + obj + } + + private static func writeObject(value: Any, ct: ClassTypeInfo, options: JsonOption, depth: Int64): JsonValue { + let obj = JsonObject() + let fields = ReflectionCache.getFields(ct, options) + for (f in fields) { + if (f.ignore) { + continue + } + let raw = f.variable.getOrThrow().getValue(value) + if (options.ignoreNull && isNoneValue(raw)) { + continue + } + obj.put(f.jsonName, writeValue(raw, options, depth + 1)) + } + obj + } + + /// 反射迭代集合:优先 toArray 转数组再 get(index);无 toArray(如 Array)直接用原集合 + private static func forEachElement(collection: Any, action: (Any) -> Unit): Unit { + let ti = TypeInfo.of(collection) + var arr: Any = collection + try { + arr = ti.getInstanceFunction("toArray", []).apply(collection, []) + } catch (_: Exception) { + // 无 toArray(如 Array):直接用原集合 get(i) + } + let arrTi = TypeInfo.of(arr) + // 仓颉集合 get(index) 返回 Option:Some(元素) / None(越界结束) + let getFunc = arrTi.getInstanceFunction("get", [TypeInfo.of()]) + var i: Int64 = 0 + var guard: Int64 = 0 + while (true) { + guard += 1 + if (guard > 1000000) { + throw Exception("JsonSerializer: 集合迭代超限 type=${TypeInfo.of(arr)}") + } + var elem: ?Any = None + try { + elem = Some(getFunc.apply(arr, [i])) + } catch (_: Exception) { + break + } + if (let Some(v) <- unwrapOptionValue(elem.getOrThrow())) { + action(v) + } else { + break + } + i += 1 + } + } + + /// 判断 Any 值是否为 None(Option 枚举的 None 构造器) + private static func isNoneValue(v: Any): Bool { + unwrapOptionValue(v).isNone() + } + + /// 若值为 Option 的 Some(x) 返回 Some(x);None 或非 Option 返回 None + private static func unwrapOptionValue(v: Any): ?Any { + if (let et: EnumTypeInfo <- TypeInfo.of(v)) { + let (ctor, values) = et.destruct(v) + if (ctor.name == "Some" && values.size > 0) { + return Some(values[0]) + } + } + None + } + + private static func toKeyString(k: Any): String { + if (let s: String <- k) { + return s + } + if (let s: ToString <- k) { + return s.toString() + } + TypeInfo.of(k).toString() + } + + /// 从 "Array" / "std.collection.HashMap" / "Option" 提取类型参数 + public static func extractTypeArgs(typeName: String): ArrayList { + var args = ArrayList() + match (typeName.indexOf("<")) { + case None => return args + case Some(i) => + var depth: Int64 = 0 + var start = i + 1 + var j = i + 1 + while (j < typeName.size) { + let b = typeName[j] + if (b == 0x3Cu8) { // '<' + depth += 1 + } else if (b == 0x3Eu8) { // '>' + if (depth == 0) { + args.add(typeName[start..j]) + return args + } + depth -= 1 + } else if (b == 0x2Cu8) { // ',' + if (depth == 0) { + args.add(typeName[start..j]) + start = j + 1 + } + } + j += 1 + } + } + args + } +} diff --git a/src/ReflectionCache.cj b/src/ReflectionCache.cj new file mode 100644 index 0000000..9c60e1a --- /dev/null +++ b/src/ReflectionCache.cj @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025 SimcuTeam. All rights reserved. + * ReflectionCache:字段元数据(含继承链、命名策略、@JsonPropertyName/@JsonIgnore)与缓存。 + * + * 已知限制(平台级): + * - InstanceVariableInfo.getValue/setValue 有「声明类严格类型校验」,父类字段无法用子类实例读写, + * 故 v1 仅枚举运行时类自身声明的 var 字段(父类字段暂不序列化)。 + */ + +package simapi_serialization + +import std.collection.* +import std.reflect.* +import simapi_serialization.attributes.* + +/** + * 单个字段的序列化元数据。 + */ +public class FieldMetadata { + /// 反射字段名(如 _name) + public var name: String = "" + /// JSON 名称(@JsonPropertyName 优先,否则按命名策略) + public var jsonName: String = "" + /// 是否忽略(@JsonIgnore) + 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>() + + /** + * 获取类型的字段元数据(含缓存)。 + * @param typeInfo 目标类类型。 + * @param options 序列化选项(命名策略)。 + * @return 字段列表(仅该类自身声明的 var 字段)。 + */ + public static func getFields(typeInfo: ClassTypeInfo, options: JsonOption): ArrayList { + let key = typeInfo.qualifiedName + if (let Some(cached) <- _cache.get(key)) { + return cached + } + var fields = ArrayList() + 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() + // @JsonIgnore + if (v.findAnnotation().isSome()) { + meta.ignore = true + } + // @JsonPropertyName 优先 + var jsonName = "" + if (let Some(jsonProp) <- v.findAnnotation()) { + jsonName = jsonProp.name + } else { + jsonName = applyNamingPolicy(v.name, options.propertyNamingPolicy) + } + meta.jsonName = jsonName + fields.add(meta) + } + _cache[key] = fields + fields + } + + /** + * 应用属性命名策略。 + */ + public static func applyNamingPolicy(fieldName: String, policy: PropertyNamingPolicy): String { + var name = fieldName + while (name.startsWith("_")) { + name = name[1..] + } + match (policy) { + case PropertyNamingPolicy.None => name + case PropertyNamingPolicy.CamelCase => toCamelCase(name) + case PropertyNamingPolicy.SnakeCase => toSnakeCase(name) + } + } + + 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() + } +} diff --git a/src/attributes/JsonAttributes.cj b/src/attributes/JsonAttributes.cj new file mode 100644 index 0000000..1fe429a --- /dev/null +++ b/src/attributes/JsonAttributes.cj @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 SimcuTeam. All rights reserved. + * 对齐 .NET System.Text.Json.Serialization 特性: + * - [JsonPropertyName("xxx")] → @JsonPropertyName["xxx"] 指定字段的 JSON 名称 + * - [JsonIgnore] → @JsonIgnore 序列化/反序列化时忽略该字段 + * + * 参考仓颉官方注解用法(std.reflect 运行时读取): + * https://docs.cangjie-lang.cn/en/docs/1.0.0/libs/std/reflect/reflect_samples/annotation.html + */ + +package simapi_serialization.attributes + +/** + * 指定字段的 JSON 名称(对齐 .NET JsonPropertyNameAttribute)。 + * 用法:@JsonPropertyName["user_name"] public var _username: String = "" + */ +@Annotation[target: [MemberVariable, MemberProperty]] +public class JsonPropertyName { + public let name: String + + public const init(name: String) { + this.name = name + } +} + +/** + * 标记字段不做 JSON 处理(对齐 .NET JsonIgnoreAttribute)。 + * 用法:@JsonIgnore public var _temp: String = "" + */ +@Annotation[target: [MemberVariable, MemberProperty]] +public class JsonIgnore { + public const init() {} +}