【问题标题】:Codable object mapping array element to string可编码对象将数组元素映射到字符串
【发布时间】:2025-12-05 09:50:01
【问题描述】:

我有一个(烦人的)情况,我的后端返回一个像这样的对象:

{
"user": {
        "name": [
            "John"
        ],
        "familyName": [
            "Johnson"
        ]
    }
}

其中每个属性都是一个数组,其中包含一个字符串作为其第一个元素。在我的数据模型struct 中,我可以将每个属性声明为一个数组,但这真的很难看。我希望我的模型是这样的:

struct User: Codable {
    var user: String
    var familyName: String
}

但这当然会使编码/解码失败,因为类型不匹配。到目前为止,我已经使用了ObjectMapper 库,它提供了一个Map 对象和currentValue 属性,我可以将我的属性声明为String 类型,并在我的模型中init 方法通过这个函数分配每个值:

extension Map {
    public func firstFromArray<T>(key: String) -> T? {
        if let array = self[key].currentValue as? [T] {
            return array.first
        }
        return self[key].currentValue as? T
    }
}

但是现在我正在转换为Codable 方法,我不知道如何进行这种映射。有什么想法吗?

【问题讨论】:

    标签: ios json swift codable decodable


    【解决方案1】:

    你可以覆盖init(from decoder: Decoder):

    let json = """
    {
        "user": {
            "name": [
            "John"
            ],
            "familyName": [
            "Johnson"
            ]
        }
    }
    """
    
    struct User: Codable {
        var name: String
        var familyName: String
    
        init(from decoder: Decoder) throws {
            let container:KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self)
            let nameArray = try container.decode([String].self, forKey: .name)
            let familyNameArray = try container.decode([String].self, forKey: .familyName)
            self.name = nameArray.first!
            self.familyName = familyNameArray.first!
        }
    
        enum CodingKeys: String, CodingKey {
            case name
            case familyName
        }
    }
    
    let data = json.data(using: .utf8)!
    let decodedDictionary = try JSONDecoder().decode(Dictionary<String, User>.self, from: data)
    print(decodedDictionary) // ["user": __lldb_expr_48.User(name: "John", familyName: "Johnson")]
    
    let encodedData = try JSONEncoder().encode(decodedDictionary["user"]!)
    let encodedStr = String(data: encodedData, encoding: .utf8)
    print(encodedStr!) // {"name":"John","familyName":"Johnson"}
    

    【讨论】:

    • 感谢您的好回答。不过,我决定采用计算属性的方式。
    【解决方案2】:

    我倾向于使您的模型适应传入的数据并创建计算属性以供在应用程序中使用,例如

    struct User: Codable {
        var user: [String]
        var familyName: [String]
    
        var userFirstName: String? {
            return user.first
        }
    
        var userFamilyName: String? {
            return familyName.first
        }
    }
    

    这使您可以轻松地使用传入的数据结构维护模仿,而无需覆盖编码/解码的维护成本。

    如果它与您的设计相得益彰,您还可以使用 UI 包装器 Type 或 ViewModel 来更清楚地区分底层模型与其显示。

    【讨论】:

    • 谢谢。我更喜欢这个而不是覆盖init