You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 12:40:37