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

Swift Decodable:如何将ID注入嵌套泛型属性并设为非可选let

问题:将Strapi v4 API响应的ID转为非可选let属性注入子对象

Strapi v4 API响应结构

{
    "data": [
        {
            "id": 1,
            "attributes": {
                "description": "test"
            }
        }
    ]    
}

现有实现代码

用户编写的泛型处理代码如下,当前StrapiDataObjectContent协议中的id是可选的var,希望改为非可选的let属性:

struct StrapiArrayResponse<Content: StrapiDataObjectContent>: Codable {
    var data: [StrapiDataObject<Content>]
}

struct StrapiDataObject<Content: StrapiDataObjectContent>: Codable {
    let id: Int
    var attributes: Content
    
    init(from decoder: Decoder) throws {
        let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)
        self.attributes = try container.decode(Content.self, forKey: .attributes)
        self.attributes.id = id
    }
}

protocol StrapiDataObjectContent: Codable {
    var id: Int! { get set } // 希望改为非可选的let属性
}

解决方案

方法一:让Content类型通过初始化器接收ID

这种方式逻辑直接,要求每个StrapiDataObjectContent实现类型必须提供带ID的初始化方法,解码时直接传递ID:

  1. 修改协议定义:
protocol StrapiDataObjectContent: Codable {
    let id: Int
    init(id: Int, from decoder: Decoder) throws
}
  1. 调整StrapiDataObject的解码逻辑:
struct StrapiDataObject<Content: StrapiDataObjectContent>: Codable {
    let id: Int
    let attributes: Content
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)
        // 获取attributes对应的解码器,传入ID初始化Content
        let attributesDecoder = try container.superDecoder(forKey: .attributes)
        self.attributes = try Content(id: id, from: attributesDecoder)
    }
    
    enum CodingKeys: String, CodingKey {
        case id, attributes
    }
}
  1. 具体Content类型实现示例:
struct Post: StrapiDataObjectContent {
    let id: Int
    let description: String
    
    init(id: Int, from decoder: Decoder) throws {
        self.id = id
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.description = try container.decode(String.self, forKey: .description)
    }
    
    enum CodingKeys: String, CodingKey {
        case description
    }
}

方法二:通过解码上下文传递ID

这种方式利用Swift解码的userInfo上下文传递ID,避免每个Content类型都重复写带ID的初始化逻辑:

  1. 定义自定义解码用户信息Key:
private struct StrapiIdKey: CodingUserInfoKey {}
  1. 调整StrapiDataObject的解码逻辑,将ID注入上下文:
struct StrapiDataObject<Content: StrapiDataObjectContent>: Codable {
    let id: Int
    let attributes: Content
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)
        
        // 创建带有ID的解码器上下文
        var attributesDecoder = try container.superDecoder(forKey: .attributes)
        attributesDecoder.userInfo[StrapiIdKey.self] = id
        
        self.attributes = try Content(from: attributesDecoder)
    }
    
    enum CodingKeys: String, CodingKey {
        case id, attributes
    }
}
  1. 修改协议并实现Content类型:
protocol StrapiDataObjectContent: Codable {
    let id: Int
}

struct Post: StrapiDataObjectContent {
    let id: Int
    let description: String
    
    init(from decoder: Decoder) throws {
        // 从上下文获取ID
        guard let id = decoder.userInfo[StrapiIdKey.self] as? Int else {
            throw DecodingError.dataCorrupted(
                DecodingError.Context(
                    codingPath: decoder.codingPath,
                    debugDescription: "Strapi ID未在解码上下文中找到"
                )
            )
        }
        self.id = id
        
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.description = try container.decode(String.self, forKey: .description)
    }
    
    enum CodingKeys: String, CodingKey {
        case description
    }
}

总结

  • 方法一优势:逻辑直观,每个Content类型的初始化逻辑清晰,不依赖外部上下文;
  • 方法二优势:Content类型代码更简洁,ID传递逻辑集中在StrapiDataObject中,但需要依赖解码上下文,调试时需注意上下文注入是否正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 04:15:39