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

Swift中如何将嵌套JSON结构解码为Data类型延迟存储

Swift中如何将嵌套JSON结构解码为Data类型延迟存储

嘿,这个需求我之前也碰到过,其实不难解决——核心就是把嵌套的JSON先转成Data存起来,等需要的时候再解码成具体模型。下面给你两种实用的实现方式,选哪种看你场景:

方案一:给Data扩展全局的Codable适配

默认情况下,Data的Codable实现是用来处理base64编码数据的,所以我们得给它写个自定义的解码/编码逻辑,让它能直接把JSON对象转成Data存储。

这里需要一个辅助类型AnyCodable来处理任意结构的JSON(因为JSON的类型不确定,可能是字典、数组或者基础类型),代码如下:

struct Message: Codable {
    let id: Int
    let data: Data
}

// 辅助类型:处理任意JSON类型的Codable适配
struct AnyCodable: Codable {
    let value: Any
    
    init(_ value: Any) {
        self.value = value
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        
        // 依次尝试解码成常见的JSON类型
        if let bool = try? container.decode(Bool.self) {
            value = bool
        } else if let int = try? container.decode(Int.self) {
            value = int
        } else if let double = try? container.decode(Double.self) {
            value = double
        } else if let string = try? container.decode(String.self) {
            value = string
        } else if let array = try? container.decode([AnyCodable].self) {
            value = array.map { $0.value }
        } else if let dict = try? container.decode([String: AnyCodable].self) {
            value = dict.mapValues { $0.value }
        } else {
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "无法识别的JSON类型")
        }
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        
        // 根据value的实际类型编码回JSON
        switch value {
        case let bool as Bool: try container.encode(bool)
        case let int as Int: try container.encode(int)
        case let double as Double: try container.encode(double)
        case let string as String: try container.encode(string)
        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: "无法编码的JSON类型"))
        }
    }
}

// 给Data扩展Codable,利用AnyCodable实现JSON转Data
extension Data: Codable {
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let jsonObj = try container.decode(AnyCodable.self)
        self = try JSONSerialization.data(withJSONObject: jsonObj.value, options: [])
    }
    
    public func encode(to encoder: Encoder) throws {
        let jsonObj = try JSONSerialization.jsonObject(with: self, options: [])
        var container = encoder.singleValueContainer()
        try container.encode(AnyCodable(jsonObj))
    }
}

这个方案的好处是全局生效,所有需要把JSON转成Data存储的地方都能用。解码后Message.data里就是原始的JSON二进制数据,以后需要的时候直接用JSONDecoder解码成具体模型就行。

方案二:仅给Message自定义解码逻辑

如果你不想修改全局的Data行为,也可以只在Message的自定义初始化方法里处理data字段的解码,这样更针对性:

struct Message: Codable {
    let id: Int
    let data: Data
    
    // 自定义解码逻辑
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)
        
        // 先把data对应的JSON转成AnyCodable,再转成Data
        let jsonObj = try container.decode(AnyCodable.self, forKey: .data)
        self.data = try JSONSerialization.data(withJSONObject: jsonObj.value, options: [])
    }
    
    // 自定义编码逻辑
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        
        // 把Data转成JSON对象再编码回去
        let jsonObj = try JSONSerialization.jsonObject(with: data, options: [])
        try container.encode(AnyCodable(jsonObj), forKey: .data)
    }
    
    enum CodingKeys: String, CodingKey {
        case id, data
    }
}

// 同样需要这个AnyCodable辅助类型(和方案一的一样)
struct AnyCodable: Codable {
    let value: Any
    
    init(_ value: Any) {
        self.value = value
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        
        if let bool = try? container.decode(Bool.self) {
            value = bool
        } else if let int = try? container.decode(Int.self) {
            value = int
        } else if let double = try? container.decode(Double.self) {
            value = double
        } else if let string = try? container.decode(String.self) {
            value = string
        } else if let array = try? container.decode([AnyCodable].self) {
            value = array.map { $0.value }
        } else if let dict = try? container.decode([String: AnyCodable].self) {
            value = dict.mapValues { $0.value }
        } else {
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "无法识别的JSON类型")
        }
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        
        switch value {
        case let bool as Bool: try container.encode(bool)
        case let int as Int: try container.encode(int)
        case let double as Double: try container.encode(double)
        case let string as String: try container.encode(string)
        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: "无法编码的JSON类型"))
        }
    }
}

这个方案的优势是不会影响其他地方的Data解码,只针对Message生效,更灵活。

使用示例

不管用哪种方案,解码后的Message都能正常存储data,之后需要解码的时候直接用JSONDecoder就行:

// 示例JSON数据
let jsonStr = """
{
    "id": 3,
    "data": {
        "name": "Bob",
        "age": 32
    }
}
"""

guard let jsonData = jsonStr.data(using: .utf8) else {
    fatalError("JSON转换失败")
}

do {
    // 解码Message
    let message = try JSONDecoder().decode(Message.self, from: jsonData)
    print("解码得到ID:\(message.id)")
    
    // 后续需要解码data时,定义对应的模型
    struct User: Codable {
        let name: String
        let age: Int
    }
    
    let user = try JSONDecoder().decode(User.self, from: message.data)
    print("用户姓名:\(user.name),年龄:\(user.age)")
} catch {
    print("解码错误:\(error)")
}

总结

  • 如果你的项目中有多处需要把JSON转成Data存储,选方案一,全局扩展更方便;
  • 如果只是Message这个结构体需要这个功能,选方案二,避免全局修改带来的潜在问题。

两种方案都能完美实现“延迟解码”的需求,先把原始JSON数据存成Data,等需要的时候再解码成具体模型。

备注:内容来源于stack exchange,提问作者wheatset

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 15:04:36