说我有以下代码:
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的年龄= realage
或28
,在第二个示例中设置nil
。在这两种情况下,我都希望age
成为nil
而不是28
。
有没有办法只使用CodingKeys
来实现这一点而不必添加另一个结构或类?如果不是,我怎么能用另一个结构或类来以最简单的方式实现我想要的呢?
答案 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
,因为它意味着Encodable
和Decodable
。您似乎只需要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(firstName
,lastName
,age
),并且两个方向都保留了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未提及的代码中包含的一个更改是您无法使用文档示例中的扩展。所以你必须将它嵌入结构或类中。
希望这可以帮助某人,以及其他惊人的答案。