如何在Swift中实现类与JSON的动态双向转换?
问题描述
我正在开发一个类RPC项目,需要处理特定结构的JSON:包含函数名字段f和参数列表p,参数列表中每个元素是带n(参数名)和v(参数值,支持基础类型、嵌套对象等任意类型)的对象。示例JSON如下:
{ "f": "sample.login", "p": [ {"n": "param1", "v": "value1"}, {"n": "param2", "v": true}, {"n": "param3", "v": {"id": 1, "title": "title xyz"}} // 支持任意数量参数 ] }
我已在C++(nlohmann json)和Kotlin(Gson)中完成实现,但Swift版本遇到了阻碍——Swift的Codable不直接支持Any?类型的编码。我尝试过苹果论坛、谷歌、Slack iOS群组均未找到可行方案,希望找到类似"让用户为自定义类型实现协议"或"注册已知类型"的解决方案。当前的Swift尝试代码如下:
public class Param: Codable { public var n: String public var v: Any? init(n: String, v: Any?) { self.n = n self.v = v } } struct Todo: Codable { var id: Int64 var title: String var data: [String: String] var done: Bool } public class JsonSerializer { private var decoder = JSONDecoder() private var encoder = JSONEncoder() public func encodeRequest(functionName: String, params: [Param]) -> String { do { let request = JsonRequestData(f: functionName, p: params) let data = try encoder.encode(request) if let result = String(data: data, encoding: .utf8) { return result } else { print("[JsonSerializer : encodeRequest] Error when try to encode data") } } catch let e { print("[JsonSerializer : encodeRequest] Error when try to encode data: \(e.localizedDescription)") } return "" } struct JsonRequestData: Codable { let f: String var p: [Param] init(f: String, p: [Param]) { self.f = f self.p = p } } } let todo = Todo(id: 1, title: "Title 1", data: [:], done: true) let name = "sample.todo.single" var params: [Param] = [] params.append(Param(n: "suffix", v: "%")) params.append(Param(n: "p2", v: todo)) let s = JsonSerializer() let json = s.encodeRequest(functionName: name, params: params) print(json)
解决方案
针对Swift的Codable限制,提供两种可行实现方案:
方案1:手动实现Param的Codable逻辑(快速落地)
通过手动编写Param的encode(to:)和init(from:)方法,针对不同类型做编码/解码处理,同时兼容自定义Codable类型:
public class Param { public var n: String public var v: Any? init(n: String, v: Any?) { self.n = n self.v = v } } extension Param: Codable { enum CodingKeys: String, CodingKey { case n, v } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(n, forKey: .n) switch v { case let value as String: try container.encode(value, forKey: .v) case let value as Int: try container.encode(value, forKey: .v) case let value as Int64: try container.encode(value, forKey: .v) case let value as Bool: try container.encode(value, forKey: .v) case let value as Double: try container.encode(value, forKey: .v) case let value as [String: Any]: try container.encode(JSONObject(dictionary: value), forKey: .v) case let value as [Any]: try container.encode(JSONArray(array: value), forKey: .v) case let value as Codable: try value.encode(to: container.superEncoder(forKey: .v)) case nil: try container.encodeNil(forKey: .v) default: throw EncodingError.invalidValue(v, EncodingError.Context(codingPath: [CodingKeys.v], debugDescription: "Unsupported type: \(type(of: v))")) } } public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) n = try container.decode(String.self, forKey: .n) let valueDecoder = try container.superDecoder(forKey: .v) if let string = try? valueDecoder.decode(String.self) { v = string } else if let int = try? valueDecoder.decode(Int.self) { v = int } else if let int64 = try? valueDecoder.decode(Int64.self) { v = int64 } else if let bool = try? valueDecoder.decode(Bool.self) { v = bool } else if let double = try? valueDecoder.decode(Double.self) { v = double } else if let jsonObj = try? valueDecoder.decode(JSONObject.self) { v = jsonObj.dictionary } else if let jsonArr = try? valueDecoder.decode(JSONArray.self) { v = jsonArr.array } else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unsupported value type")) } } } // 辅助类型:处理字典的Codable转换 struct JSONObject: Codable { let dictionary: [String: Any] init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: DynamicCodingKey.self) var dict = [String: Any]() for key in container.allKeys { if let str = try? container.decode(String.self, forKey: key) { dict[key.stringValue] = str } else if let int = try? container.decode(Int.self, forKey: key) { dict[key.stringValue] = int } else if let int64 = try? container.decode(Int64.self, forKey: key) { dict[key.stringValue] = int64 } else if let bool = try? container.decode(Bool.self, forKey: key) { dict[key.stringValue] = bool } else if let double = try? container.decode(Double.self, forKey: key) { dict[key.stringValue] = double } else if let nestedObj = try? container.decode(JSONObject.self, forKey: key) { dict[key.stringValue] = nestedObj.dictionary } else if let nestedArr = try? container.decode(JSONArray.self, forKey: key) { dict[key.stringValue] = nestedArr.array } } dictionary = dict } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: DynamicCodingKey.self) for (key, value) in dictionary { let codingKey = DynamicCodingKey(stringValue: key)! switch value { case let str as String: try container.encode(str, forKey: codingKey) case let int as Int: try container.encode(int, forKey: codingKey) case let int64 as Int64: try container.encode(int64, forKey: codingKey) case let bool as Bool: try container.encode(bool, forKey: codingKey) case let double as Double: try container.encode(double, forKey: codingKey) case let dict as [String: Any]: try container.encode(JSONObject(dictionary: dict), forKey: codingKey) case let arr as [Any]: try container.encode(JSONArray(array: arr), forKey: codingKey) default: throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [codingKey], debugDescription: "Unsupported dictionary value type")) } } } } // 辅助类型:处理数组的Codable转换 struct JSONArray: Codable { let array: [Any] init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() var arr = [Any]() while !container.isAtEnd { if let str = try? container.decode(String.self) { arr.append(str) } else if let int = try? container.decode(Int.self) { arr.append(int) } else if let int64 = try? container.decode(Int64.self) { arr.append(int64) } else if let bool = try? container.decode(Bool.self) { arr.append(bool) } else if let double = try? container.decode(Double.self) { arr.append(double) } else if let nestedObj = try? container.decode(JSONObject.self) { arr.append(nestedObj.dictionary) } else if let nestedArr = try? container.decode(JSONArray.self) { arr.append(nestedArr.array) } } array = arr } func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() for value in array { switch value { case let str as String: try container.encode(str) case let int as Int: try container.encode(int) case let int64 as Int64: try container.encode(int64) case let bool as Bool: try container.encode(bool) case let double as Double: try container.encode(double) case let dict as [String: Any]: try container.encode(JSONObject(dictionary: dict)) case let arr as [Any]: try container.encode(JSONArray(array: arr)) default: throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: container.codingPath, debugDescription: "Unsupported array value type")) } } } } // 动态CodingKey:处理未知键名 struct DynamicCodingKey: CodingKey { let stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? init?(intValue: Int) { return nil } }
修改后的JsonSerializer可以直接复用原有逻辑,测试代码运行后会生成符合要求的JSON。
方案2:协议扩展+类型注册(灵活扩展)
通过自定义协议让用户类型主动适配,结合类型注册解决协议解码问题:
// 定义RPC参数值协议 protocol RPCParamValue: Codable {} // 让基础类型自动遵循协议 extension String: RPCParamValue {} extension Int: RPCParamValue {} extension Int64: RPCParamValue {} extension Bool: RPCParamValue {} extension Double: RPCParamValue {} extension [String: RPCParamValue]: RPCParamValue {} extension [RPCParamValue]: RPCParamValue {} // 修改Param类,使用协议类型替代Any? public class Param { public var n: String public var v: RPCParamValue? init(n: String, v: RPCParamValue?) { self.n = n self.v = v } } extension Param: Codable { enum CodingKeys: String, CodingKey { case n, v } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(n, forKey: .n) try container.encode(RPCParamValueWrapper(v), forKey: .v) } public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) n = try container.decode(String.self, forKey: .n) v = try container.decode(RPCParamValueWrapper.self, forKey: .v).value } } // 类型擦除包装器:解决协议解码问题 struct RPCParamValueWrapper: Codable { let value: RPCParamValue init(_ value: RPCParamValue?) { self.value = value ?? EmptyParamValue() } enum CodingKeys: String, CodingKey { case type, value } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) let typeName = String(describing: type(of: value)) try container.encode(typeName, forKey: .type) try value.encode(to: container.superEncoder(forKey: .value)) } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let typeName = try container.decode(String.self, forKey: .type) guard let type = RPCParamTypeRegistry.shared.type(for: typeName) as? RPCParamValue.Type else { throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Unknown type: \(typeName)") } let valueDecoder = try container.superDecoder(forKey: .value) value = try type.init(from: valueDecoder) } } // 空参数值:处理nil情况 struct EmptyParamValue: RPCParamValue {} // 类型注册器:管理自定义类型映射 class RPCParamTypeRegistry { static let shared = RPCParamTypeRegistry() private var typeMap = [String: Any.Type]() func register<T: RPCParamValue>(_ type: T.Type) { typeMap[String(describing: type)] = type } func type(for name: String) -> Any.Type? { typeMap[name] } } // 自定义类型只需遵循RPCParamValue struct Todo: RPCParamValue { var id: Int64 var title: String var data: [String: String] var done: Bool } // 使用前注册类型 RPCParamTypeRegistry.shared.register(Todo.self) RPCParamTypeRegistry.shared.register(EmptyParamValue.self)
这种方案要求用户的自定义类型遵循RPCParamValue并提前注册,扩展性更强,适合需要大量自定义类型的场景。
内容的提问来源于stack exchange,提问作者Paulo Coutinho
相关产品推荐
相关产品推荐

