【问题标题】:How to write the codable in generic format [duplicate]如何以通用格式编写可编码[重复]
【发布时间】:2020-09-07 08:28:13
【问题描述】:

我已经了解如何为服务响应结构制作可编码的包装类。 但有时在服务器端属性值会发生变化,它可能是 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 string 不管它应该使用 id 值数据绑定到我的控制器,我想使用通用方式。

let id : <T>

如何以通用格式编写可编码。

【问题讨论】:

标签: swift generics codable


【解决方案1】:

您可以使用以下模型来做到这一点:

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)
    }
}

【讨论】:

    【解决方案2】:

    首先,这里是使用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)
            }
        }
    }
    

    【讨论】:

    • 这不会有预期的结果。如果idStringdecodeIfPresent(_:forKey:) 函数将抛出DecodingError.typeMismatch 错误,并且else 语句中的任何内容都将永远不会被执行。
    • 此外,这种方法(更新后)会在应该引发错误时忽略格式错误的 JSON。
    • @gcharita 不,不会的。这将在解析 data 时由 catch 语句处理。
    • decode(_:forKey:) 的 @gcharita do-catch 块将仅作为提前退出,因为它会抛出以下之一 - typeMismatchkeyNotFoundvalueNotFound 用于该特定键值对
    • @PGDev 你是对的,错误将从第二个try 抛出。 (因为当 JSON 格式错误时,try? 将返回 nil
    【解决方案3】:

    根据提供的@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 illustrated,您也可以捕获DecodingError,但decode(_:forKey:) 的do-catch 语句只能作为提前退出,因为它会引发以下错误之一-typeMismatch、@987654328 @ 或 valueNotFound 用于特定的键值对。

    【讨论】:

    • 为什么不投票关闭这个副本呢?
    猜你喜欢
    • 2021-05-05
    • 2015-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-17
    相关资源
    最近更新 更多