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

问题描述 投票:0回答:6

假设我有以下代码:

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
。这是有道理的。我的问题是如何在第一个示例中设置人的年龄 =
realage
28
以及第二个示例中的
nil
。我希望在第一种情况下它是
age
,而不是在这两种情况下都是
nil

有没有办法只使用

28

来实现这一点,而不必添加另一个结构或类?如果不是,我如何使用另一个结构或类以最简单的方式实现我想要的?

    

json swift swift4 codable
6个回答
5
投票
CodingKeys

。它有助于将 JSON 数据快速导入 Swift,然后您可以使用 Swift 进行所需的操作:


snake_case

此外,我建议您谨慎使用 
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
Encodable
。看来您只需要
Decodable
,因此只需使您的模型符合该协议。
    


3
投票

Decodable

这里保留了 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) }

firstName
lastName
),并且在两个方向上都保留了 JSON。
    


3
投票
age

枚举来完全支持您的数据模型;)例如:


Age

然后在您的 
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 } } }

打印:

Person(名字:“汤姆”,姓氏:“史密斯”,年龄:Age.realAge(“28”))
人(名字:“鲍勃”,姓氏:“史密斯”,年龄:Age.fakeAge(“31”))


最后,新的
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) }

计算属性提供了您最初想要的行为(即,对于真实年龄,非零

only
): realAge

  
汤姆可选(“28”)
鲍勃·尼尔



2
投票

for person in decoded { print(person.firstName, person.realAge) }

您可以这样打电话:

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



1
投票

我相信其他人会发现这篇文章很有用,这真是太棒了。除此之外,我将发布我的解决方案,说明我决定如何执行此操作。

查看

编码和解码自定义类型Apple文档

后,我发现可以构建自定义解码器和编码器来实现此目的(手动编码和解码)。 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") }

上述代码中包含的 Apple 未提及的一项更改是,您不能像其文档示例中那样使用扩展。所以你必须将它嵌入到结构或类中。

希望这对某人有帮助,以及这里的其他令人惊奇的答案。


0
投票
struct Coordinate: Codable { var latitude: Double var longitude: Double var elevation: Double enum CodingKeys: String, CodingKey { case latitude case longitude case additionalInfo } enum AdditionalInfoKeys: String, CodingKey { case elevation } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) latitude = try values.decode(Double.self, forKey: .latitude) longitude = try values.decode(Double.self, forKey: .longitude) let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo) elevation = try additionalInfo.decode(Double.self, forKey: .elevation) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(latitude, forKey: .latitude) try container.encode(longitude, forKey: .longitude) var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo) try additionalInfo.encode(elevation, forKey: .elevation) } }

移动到

struct Age
内部,因此“隐藏”它,有点按照OP的要求,不要添加更多数据类型。这里的答案还提供了一个更简单的界面:
Person
。只有年龄本身是可选的。
.age

// include the jsonData from the question here struct Person: Decodable { let firstName, lastName: String private var _age: Age var age: String? { return _age.realage } struct Age: Decodable { let realage: String? } enum CodingKeys: String, CodingKey { case firstName = "firstname" case lastName = "lastname" case _age = "age" } } let people = try! JSONDecoder().decode([Person].self, from: jsonData) // people.first._age // '_age' is inaccessible due to 'private' protection level people.map { person in "\(person.firstName) \(person.lastName) is \(person.age ?? "??")" }

只是让在 Playground 中查看所有数据变得更容易。)

    

© www.soinside.com 2019 - 2024. All rights reserved.