假设我有以下代码:
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
来实现这一点,而不必添加另一个结构或类?如果不是,我如何使用另一个结构或类以最简单的方式实现我想要的?
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
,因此只需使您的模型符合该协议。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。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”)
鲍勃·尼尔
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?
}
我相信其他人会发现这篇文章很有用,这真是太棒了。除此之外,我将发布我的解决方案,说明我决定如何执行此操作。
查看
编码和解码自定义类型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 未提及的一项更改是,您不能像其文档示例中那样使用扩展。所以你必须将它嵌入到结构或类中。
希望这对某人有帮助,以及这里的其他令人惊奇的答案。
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 中查看所有数据变得更容易。)