如何以通用格式编写代码

How to write the codable in generic format

我已经了解如何为服务响应结构制作可编码包装器 class。 但有时在服务器端属性值会有所不同,它可能是 Int 或 String。

例子

struct ResponseDataModel : Codable{
    let data : DataClass?
    enum  CodingKey: String, CodingKey{
        case data = "data"

    }
    init(from decoder: Decoder) throw {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        data = try values.decodeIfPresent(DataClass.self, forKey:.data)
    }
}

struct DataClass : Codable{
    let id : Int
    let name : String?
    let age : Int?

    enum  CodingKey: String, CodingKey{
        case id = "id"
        case name = "name"
        case age = "age"

    }
    init(from decoder: Decoder) throw {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decodeIfPresent(Int.self, forKey:.it)
        name = try values.decodeIfPresent(String.self, forKey:.name)
        age = try values.decodeIfPresent(Int.self, forKey:.age)
    }
}

如果 id int 字符串,我想使用通用方式,无论它应该使用 id 值数据绑定到我的控制器。

let id : <T>

如何用通用格式编写可编码文件。

您可以使用以下模型执行此操作:

struct ResponseDataModel<T: Codable>: Codable{
    let data : DataClass<T>?
    enum  CodingKeys: String, CodingKey{
        case data = "data"

    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        data = try values.decodeIfPresent(DataClass<T>.self, forKey:.data)
    }
}

struct DataClass<T: Codable>: Codable {
    let id: T?
    let name: String?
    let age: Int?

    enum  CodingKeys: String, CodingKey{
        case id = "id"
        case name = "name"
        case age = "age"

    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decodeIfPresent(T.self, forKey:.id)
        name = try values.decodeIfPresent(String.self, forKey:.name)
        age = try values.decodeIfPresent(Int.self, forKey:.age)
    }
}

但是,当您像这样调用 JSONDecoderdecode(_:from:) 函数时,您应该始终知道 id 属性 的类型:

let decoder = JSONDecoder()

do {
    let decoded = try decoder.decode(ResponseDataModel<Int>.self, from: data)
    print(decoded)
} catch {
    print(error)
}

或者您可以使用以下模型始终将 id 映射为 Int,即使您的服务器将其发送为 String:

struct ResponseDataModel: Codable{
    let data : DataClass?
    enum  CodingKeys: String, CodingKey{
        case data = "data"

    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        data = try values.decodeIfPresent(DataClass.self, forKey:.data)
    }
}

struct DataClass: Codable {
    let id: Int?
    let name: String?
    let age: Int?

    enum  CodingKeys: String, CodingKey{
        case id = "id"
        case name = "name"
        case age = "age"

    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        do {
            id = try values.decodeIfPresent(Int.self, forKey:.id)
        } catch DecodingError.typeMismatch {
            if let idString = try values.decodeIfPresent(String.self, forKey:.id) {
                id = Int(idString)
            } else {
                id = nil
            }
        }
        name = try values.decodeIfPresent(String.self, forKey:.name)
        age = try values.decodeIfPresent(Int.self, forKey:.age)
    }
}

根据提供的 @Joakim Danielson 示例,您可以通过尝试解码每种类型的值来获得所需的结果。

struct Response: Decodable {
    let id: String
    let name: String?
    let age: Int?
    
    private enum CodingKeys: String, CodingKey {
        case data
    }
    
    private enum NestedCodingKeys: String, CodingKey {
        case id
        case name
        case age
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let nestedContainer = try container.nestedContainer(
            keyedBy: NestedCodingKeys.self,
            forKey: .data
        )
        
        if let id = try? nestedContainer.decode(Int.self, forKey: .id) {
            self.id = String(id)
        } else {
            id = try nestedContainer.decode(String.self, forKey: .id)
        }
        
        name = try nestedContainer.decodeIfPresent(String.self, forKey: .name)
        age = try nestedContainer.decodeIfPresent(Int.self, forKey: .age)
    }
}

@gcharita 一样,您也可以捕获 DecodingError,但是 do-catch 对 decode(_:forKey:) 的语句只会作为提前退出,因为它会抛出以下错误之一- typeMismatchkeyNotFoundvalueNotFound 用于特定的 key-value 对。

首先,这里有一些使用Codable进行解析时需要注意的关键点。

  1. 如果 属性 名称和键的名称完全相同,则无需每次都执行 enum CodingKeys

  2. 另外,如果没有特定的解析需求,则不需要实现init(from:)Codable 如果模型按照格式正确编写,将自动处理所有解析。

因此,通过以上 2 项改进,您的 ResponseDataModel 看起来像,

struct ResponseDataModel : Codable{
    let data: DataClass?
}

现在,对于 DataClass,您只需添加一个 if-else 条件来处理 IntString 情况。这里不需要实施 泛型

使用 StringInt 作为 id 的类型。并相应地添加条件。在下面的代码中,我使用 id 作为 String.

struct DataClass : Codable {
    let id : String //here....
    let name : String?
    let age : Int?
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decodeIfPresent(String.self, forKey: .name)
        age = try values.decodeIfPresent(Int.self, forKey: .age)

        if let id = try? values.decode(Int.self, forKey: .id) {
            self.id = String(id)
        } else {
            self.id = try values.decode(String.self, forKey:.id)
        }
    }
}