【问题标题】:Swift Codable: dealing with Optimized JSON generating type MismatchSwift Codable:处理优化的 JSON 生成类型不匹配
【发布时间】:2018-03-12 16:38:10
【问题描述】:

我正在尝试找出处理发送优化 JSON 的 API 的正确或最佳方法。 如果您还没有将优化的 JSON 作为服务器响应,这里有一点背景知识,假设您有一个响应 #color 代码为 #FF2233 服务器发送 {"color" : "FF2233"}, 但是如果代码是 223344,那么服务器会发出 {"color" : 223344} 去掉引号 对于单个对象数组相同,它发送不带括号的对象 到目前为止,这是我在操场上的测试......它有效,但在我看来,这不是解决我要解决的问题的最佳方法。

有没有办法在尝试解码之前检查值的类型,以便我们至少可以尝试正确转换它??

struct Test : Codable {

    var a : String?

    enum CodingKeys: String, CodingKey {
        case a = "a"
    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        do{
            a = try values.decodeIfPresent(String.self, forKey: .a)
        }
        catch{
           a = String(describing: try values.decodeIfPresent(Int.self, forKey: .a)!)
        }
    }

    init(a: String)
    {
        self.a = a
    }
}

var a = Test(a: "FF2233")
var jd = try? JSONEncoder().encode(a)
var jt = String(bytes: jd!, encoding: .utf8)
jt = "{\"a\":223365}"
jd = jt?.data(using: .utf8)
do{
    let res = try JSONDecoder().decode(Test.self, from: jd!)
    print(res.a!)
} catch{
    print(error)
}

【问题讨论】:

    标签: swift codable


    【解决方案1】:

    严格来说,您在这里处理的是损坏的 JSON。你的方法是必要的。这很愚蠢,但这只是因为有人做出了一个愚蠢的决定来向您发送损坏的 JSON。

    您可以通过扩展KeyedDecodingContainer 来稍微清理一下代码,以将这些更改封装在一个名为lenientDecode() 之类的方法中。然后你可以写a = values.lenientDecode(String.self, forKey:.a)之类的东西。您仍然会执行相同的检查,但将它们放在单独的方法中会更容易重复对多个字段的检查。

    【讨论】:

    【解决方案2】:

    我在尝试处理您的数据类型时发现它很有趣。

    让我们首先将其分解为底层类型。在最低级别,您有Int 或String。然后你有Single 对象或Array。在最高级别,您应该有一个 struct 可以处理您的 Root 对象。基本上你需要两个enums 来包装你的struct。让我们试试:

    enum IntOrString: Codable {
        case int(Int)
        case string(String)
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            do {
                self = try .int(container.decode(Int.self))
            } catch DecodingError.typeMismatch {
                do {
                    self = try .string(container.decode(String.self))
                } catch DecodingError.typeMismatch {
                    throw DecodingError.typeMismatch(IntOrString.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Int or String)"))
                }
            }
        }
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .int(let int):
                try container.encode(int)
            case .string(let string):
                try container.encode(string)
            }
        }
    }
    
    enum SingleOrArray: Codable {
        case single(IntOrString)
        case array([IntOrString])
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            do {
                self = try .single(container.decode(IntOrString.self))
            } catch DecodingError.typeMismatch {
                do {
                    self = try .array(container.decode([IntOrString].self))
                } catch DecodingError.typeMismatch {
                    throw DecodingError.typeMismatch(SingleOrArray.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type"))
                }
            }
        }
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .single(let single):
                try container.encode(single)
            case .array(let array):
                try container.encode(array)
            }
        }
    }
    
    struct Root: Codable {
        let color: SingleOrArray
    }
    

    解码过程:

    let jsonData = """
    {
     "color":["FF2233", "FF2234", "FF2235"]
    }
    """.data(using: .utf8)!
    do {
        let response = try JSONDecoder().decode(Root.self, from: jsonData)
        //print(response)    //["FF2233", "FF2234", "FF2235"]
    
        // If you want to get underlying elements from this structure, you might do something like below
        // This is your single object
        if case .single(let single) = response.color {
            // Every single object may be an Int or a String
            if case .int(let int) = single {
                print(int)
            }
            if case .string(let string) = single {
                print(string)
            }
        }
    
        // This is your array
        if case .array(let array) = response.color {
            array.forEach({ (element) in
                // Each element of your array may be an Int or a String
                if case .int(let int) = element {
                    print(int)
                }
                if case .string(let string) = element {
                    print(string)
                }
            })
        }
    } catch {
        print(error)
    }
    

    它可以处理的数据类型有:

    您可以尝试用这些对象之一替换上述 JSON 中键 color 的值

    1. String:"FF2233"
    2. Int:223344
    3. [String]:["FF2233", "FF2234", "FF2235"]
    4. [Int]:[223344, 223345, 223346]
    5. String/Int 混合数组:["FF2233", "FF2234", 223344, 223345, "FF2235", 223346]

    这个设计最令人惊讶的事实是你可以解析[Int & String]的Mixture

    【讨论】:

    • 嗨@nayem,你能告诉我如何为 IntOrString 变量赋值吗?
    • 是的@arunit21,你可以通过let some = IntOrString.int(7)来做到这一点。
    • 嗨@nayem,谢谢你,我也发现在我的情况下我确实喜欢这个,
    • user.id = .int((globalUser?.id)!)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-12
    • 1970-01-01
    • 2021-12-29
    • 2019-03-11
    • 2018-06-09
    相关资源
    最近更新 更多