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

Swift中如何解码不同类型的JSONRPC消息?

解决JSON-RPC消息中Result字段多类型兼容解码问题

核心思路

通过自定义枚举类型处理result的两种可能类型(字符串/字典),同时借助AnyCodable辅助类型实现任意类型的Codable兼容,既满足解码需求,又不破坏现有功能。

步骤1:定义兼容多类型的枚举与辅助结构体

// 处理Result的两种类型
enum RPCResult: Codable {
    case string(String)
    case dictionary([String: Any])
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        // 优先尝试解码为字符串
        if let str = try? container.decode(String.self) {
            self = .string(str)
        } 
        // 否则尝试解码为字典(借助AnyCodable处理任意值类型)
        else if let codableDict = try? container.decode([String: AnyCodable].self) {
            self = .dictionary(codableDict.mapValues { $0.value })
        } 
        // 不支持的类型抛出错误
        else {
            throw DecodingError.typeMismatch(
                RPCResult.self, 
                DecodingError.Context(
                    codingPath: decoder.codingPath, 
                    debugDescription: "不支持的Result类型"
                )
            )
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let str):
            try container.encode(str)
        case .dictionary(let dict):
            let codableDict = dict.mapValues(AnyCodable.init)
            try container.encode(codableDict)
        }
    }
}

// 辅助结构体:实现任意Codable类型的包装
struct AnyCodable: Codable {
    let value: Any
    
    init<T>(_ value: T) where T: Codable {
        self.value = value
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        // 依次尝试解码常见类型
        if let int = try? container.decode(Int.self) {
            self.value = int
        } else if let bool = try? container.decode(Bool.self) {
            self.value = bool
        } else if let string = try? container.decode(String.self) {
            self.value = string
        } else if let double = try? container.decode(Double.self) {
            self.value = double
        } else if let array = try? container.decode([AnyCodable].self) {
            self.value = array.map { $0.value }
        } else if let dict = try? container.decode([String: AnyCodable].self) {
            self.value = dict.mapValues { $0.value }
        } else {
            throw DecodingError.typeMismatch(
                AnyCodable.self, 
                DecodingError.Context(
                    codingPath: decoder.codingPath, 
                    debugDescription: "不支持的类型"
                )
            )
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch value {
        case let int as Int: try container.encode(int)
        case let bool as Bool: try container.encode(bool)
        case let string as String: try container.encode(string)
        case let double as Double: try container.encode(double)
        case let array as [Any]:
            try container.encode(array.map(AnyCodable.init))
        case let dict as [String: Any]:
            try container.encode(dict.mapValues(AnyCodable.init))
        default:
            throw EncodingError.invalidValue(
                value, 
                EncodingError.Context(
                    codingPath: encoder.codingPath, 
                    debugDescription: "不支持的类型"
                )
            )
        }
    }
}

步骤2:修改原JsonRPCMessage结构体

更新结构体,将result字段替换为自定义枚举类型,并补充params字段(兼容示例中的通知类消息):

struct JsonRPCMessage: Codable {
    let jsonrpc: String
    let result: RPCResult?
    let method: String?
    let id: Int?
    let params: [[String: Any]]? // 处理通知类消息的参数
}

步骤3:解码并处理不同类型的Result

解码后通过枚举匹配判断result类型,分别处理字符串或字典场景:

do {
    let decoder = JSONDecoder()
    let message = try decoder.decode(JsonRPCMessage.self, from: data!)
    
    // 处理Result字段
    switch message.result {
    case .some(.string(let str)):
        // 字符串类型Result,如"ok"
        print("字符串Result: \(str)")
    case .some(.dictionary(let dict)):
        // 字典类型Result,按需访问键值对
        if let klippyConnected = dict["klippy_connected"] as? Bool {
            print("Klippy连接状态: \(klippyConnected)")
        }
        if let apiVersion = dict["api_version"] as? [Int] {
            print("API版本: \(apiVersion.joined(separator: "."))")
        }
    case .none:
        // 无Result字段(如通知类消息)
        print("无Result字段")
    }
    
    // 处理通知类消息(如notify_proc_stat_update)
    if let method = message.method, 
       method == "notify_proc_stat_update", 
       let params = message.params?.first {
        if let moonrakerStats = params["moonraker_stats"] as? [String: Any] {
            let cpuUsage = moonrakerStats["cpu_usage"] as? Double ?? 0
            print("Moonraker CPU使用率: \(cpuUsage)%")
        }
    }
} catch {
    print("解码错误: \(error)")
}

可选:解码前提前判断Result类型

如果需要在解码前先确认result的类型,可以先将Data转为JSON对象:

if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any] {
    guard let result = json["result"] else { return }
    
    switch result {
    case is String:
        print("Result是字符串类型")
    case is [String: Any]:
        print("Result是字典类型")
    default:
        print("未知的Result类型")
    }
}

内容的提问来源于stack exchange,提问作者charlespick

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 18:55:42