使用具有多个键的可解码协议

时间:2017-10-13 06:17:55

标签: json swift swift4 codable

说我有以下代码:

import Foundation

let jsonData = """
[
    {"firstname": "Tom", "lastname": "Smith", "age": {"realage": "28"}},
    {"firstname": "Bob", "lastname": "Smith", "age": {"fakeage": "31"}}
]
""".data(using: .utf8)!

struct Person: Codable {
    let firstName, lastName: String
    let age: String?

    enum CodingKeys : String, CodingKey {
        case firstName = "firstname"
        case lastName = "lastname"
        case age
    }
}

let decoded = try JSONDecoder().decode([Person].self, from: jsonData)
print(decoded)

一切正常,但age始终为nil。这是有道理的。我的问题是如何在第一个示例中设置Person的年龄= realage28,在第二个示例中设置nil。在这两种情况下,我都希望age成为nil而不是28

有没有办法只使用CodingKeys来实现这一点而不必添加另一个结构或类?如果不是,我怎么能用另一个结构或类来以最简单的方式实现我想要的呢?

5 个答案:

答案 0 :(得分:3)

在解码嵌套JSON数据时,我最喜欢的方法是定义一个非常接近JSON的“原始”模型,如果需要,甚至使用snake_case。它有助于将JSON数据快速传入Swift,然后您可以使用Swift进行所需的操作:

struct Person: Decodable {
    let firstName, lastName: String
    let age: String?

    // This matches the keys in the JSON so we don't have to write custom CodingKeys    
    private struct RawPerson: Decodable {
        struct RawAge: Decodable {
            let realage: String?
            let fakeage: String?
        }

        let firstname: String
        let lastname: String
        let age: RawAge
    }

    init(from decoder: Decoder) throws {
        let rawPerson  = try RawPerson(from: decoder)
        self.firstName = rawPerson.firstname
        self.lastName  = rawPerson.lastname
        self.age       = rawPerson.age.realage
    }
}

此外,我建议您谨慎使用Codable,因为它意味着EncodableDecodable。您似乎只需要Decodable,因此只能将您的模型与该协议相符合。

答案 1 :(得分:2)

为了获得更大的灵活性和健壮性,您可以实现enum Age: Decodable { case realAge(String) case fakeAge(String) private enum CodingKeys: String, CodingKey { case realAge = "realage", fakeAge = "fakeage" } init(from decoder: Decoder) throws { let dict = try decoder.container(keyedBy: CodingKeys.self) if let age = try dict.decodeIfPresent(String.self, forKey: .realAge) { self = .realAge(age) return } if let age = try dict.decodeIfPresent(String.self, forKey: .fakeAge) { self = .fakeAge(age) return } let errorContext = DecodingError.Context( codingPath: dict.codingPath, debugDescription: "Age decoding failed" ) throw DecodingError.keyNotFound(CodingKeys.realAge, errorContext) } } 枚举以正面支持您的数据模型;)例如:

Person

然后在struct Person: Decodable { let firstName, lastName: String let age: Age enum CodingKeys: String, CodingKey { case firstName = "firstname" case lastName = "lastname" case age } var realAge: String? { switch age { case .realAge(let age): return age case .fakeAge: return nil } } } 类型中使用它:

let jsonData = """
[
    {"firstname": "Tom", "lastname": "Smith", "age": {"realage": "28"}},
    {"firstname": "Bob", "lastname": "Smith", "age": {"fakeage": "31"}}
]
""".data(using: .utf8)!

let decoded = try! JSONDecoder().decode([Person].self, from: jsonData)
for person in decoded { print(person) }

像以前一样解码:

realAge

打印:

  

人(firstName:" Tom",lastName:" Smith&#34 ;, age:Age.realAge(" 28"))
  人(firstName:" Bob",lastName:" Smith&#34 ;, age:Age.fakeAge(" 31"))

最后,新的for person in decoded { print(person.firstName, person.realAge) } 计算属性提供了您最初的行为(即,实际年龄的非零 ):

Session
  

汤姆可选(" 28")
  Bob nil

答案 2 :(得分:1)

有时候会欺骗API来获取你想要的界面。

let jsonData = """
[
    {"firstname": "Tom", "lastname": "Smith", "age": {"realage": "28"}},
    {"firstname": "Bob", "lastname": "Smith", "age": {"fakeage": "31"}}
]
""".data(using: .utf8)!

struct Person: Codable {
    let firstName: String
    let lastName: String
    var age: String? { return _age["realage"] }

    enum CodingKeys: String, CodingKey {
        case firstName = "firstname"
        case lastName = "lastname"
        case _age = "age"
    }

    private let _age: [String: String]
}

do {
    let decoded = try JSONDecoder().decode([Person].self, from: jsonData)
    print(decoded)

    let encoded = try JSONEncoder().encode(decoded)
    if let encoded = String(data: encoded, encoding: .utf8) { print(encoded) }
} catch {
    print(error)
}

这里保留了API(firstNamelastNameage),并且两个方向都保留了JSON。

答案 3 :(得分:1)

您可以这样使用:

struct Person: Decodable {
    let firstName, lastName: String
    var age: Age?

    enum CodingKeys: String, CodingKey {
        case firstName = "firstname"
        case lastName = "lastname"
        case age
    }
}

struct Age: Decodable {
    let realage: String?
}

您可以这样打电话:

do {
    let decoded = try JSONDecoder().decode([Person].self, from: jsonData)
    print(decoded[0].age?.realage) // Optional("28")
    print(decoded[1].age?.realage) // nil
} catch {
    print("error")
}

答案 4 :(得分:1)

这里有很多很棒的答案。我有一些理由不想把它变成自己的数据模型。特别是在我的情况下,它附带了许多我不需要的数据,而且我需要的这个特定事物更多地与人而不是年龄模型相对应。

我相信其他人会发现这篇文章很有用,这太棒了。只是为了补充一点,我将发布我的解决方案,我决定如何做到这一点。

在查看Encoding and Decoding Custom Types Apple Documentation之后,我发现可以构建一个自定义解码器和编码器来实现这一点(手动编码和解码)。

{{1}}

Apple未提及的代码中包含的一个更改是您无法使用文档示例中的扩展。所以你必须将它嵌入结构或类中。

希望这可以帮助某人,以及其他惊人的答案。