使用自定义初始化程序解码 Swift 5 中的嵌套对象
Decode a nested object in Swift 5 with custom initializer
我有一个 API,其中 returns 一个这样的有效载荷(示例中只包含一个项目)。
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
我知道我实际上可以创建一个像
这样的结构
struct Root: Decodable {
let data: [DailyCount]
}
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
对于不同的调用,相同的API returns根的格式相同,只是数据不同而已。此外,我不需要根信息(length
、totalRecords
、maxPageLimit
)。
因此,我正在考虑在 struct DailyCount
中创建自定义初始化,以便我可以在我的 URL 会话
中使用它
let reports = try! JSONDecoder().decode([DailyCount].self, from: data!)
使用 Swift 5 我试过这个:
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
extension DailyCount {
enum CodingKeys: String, CodingKey {
case data
enum DailyCountCodingKeys: String, CodingKey {
case date
case peopleCount
}
}
init(from decoder: Decoder) throws {
// This should let me access the `data` container
let container = try decoder.container(keyedBy: CodingKeys.self
peopleCount = try container.decode(Int.self, forKey: . peopleCount)
date = try container.decode(String.self, forKey: .date)
}
}
不幸的是,它不起作用。我遇到两个问题:
- 该结构似乎不再符合
Decodable
协议
CodingKeys
不包含peopleCount
(因此returns出错)
由于多种原因,这行不通。您正在尝试解码一个数组,因此根本不会调用 DailyCount 中的自定义解码实现(如果要编译的话),因为在顶层,您的 JSON 包含一个对象,而不是一个数组。
但是有一个更简单的解决方案,甚至不需要您自己实现 Decodable。
您可以为您的外部对象创建一个通用的包装器结构,并将其用于您需要的任何负载类型:
struct Wrapper<Payload: Decodable>: Decodable {
var data: Payload
}
然后您可以使用它来解码您的 DailyCount 结构数组:
let reports = try JSONDecoder().decode(Wrapper<[DailyCount]>.self, from: data).data
通过在 JSON解码器上创建扩展可以使这更加透明:
extension JSONDecoder {
func decode<T: Decodable>(payload: T.Type, from data: Data) throws -> T {
try decode(Wrapper<T>.self, from: data).data
}
}
Sven 的回答纯粹而优雅,但如果我没有指出还有一种愚蠢但简单的方法,那就是我的失职:根本不使用 Codable 的垃圾箱-潜入 "data"
。示例:
// preconditions
let json = """
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
"""
let jsonData = json.data(using: .utf8)!
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
// okay, here we go
do {
let dict = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [AnyHashable:Any]
let arr = dict?["data"] as? Array<Any>
let json2 = try JSONSerialization.data(withJSONObject: arr as Any, options: [])
let output = try JSONDecoder().decode([DailyCount].self, from: json2)
print(output) // yep, it's an Array of DailyCount
} catch {
print(error)
}
我有一个 API,其中 returns 一个这样的有效载荷(示例中只包含一个项目)。
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
我知道我实际上可以创建一个像
这样的结构struct Root: Decodable {
let data: [DailyCount]
}
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
对于不同的调用,相同的API returns根的格式相同,只是数据不同而已。此外,我不需要根信息(length
、totalRecords
、maxPageLimit
)。
因此,我正在考虑在 struct DailyCount
中创建自定义初始化,以便我可以在我的 URL 会话
let reports = try! JSONDecoder().decode([DailyCount].self, from: data!)
使用 Swift 5 我试过这个:
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
extension DailyCount {
enum CodingKeys: String, CodingKey {
case data
enum DailyCountCodingKeys: String, CodingKey {
case date
case peopleCount
}
}
init(from decoder: Decoder) throws {
// This should let me access the `data` container
let container = try decoder.container(keyedBy: CodingKeys.self
peopleCount = try container.decode(Int.self, forKey: . peopleCount)
date = try container.decode(String.self, forKey: .date)
}
}
不幸的是,它不起作用。我遇到两个问题:
- 该结构似乎不再符合
Decodable
协议 CodingKeys
不包含peopleCount
(因此returns出错)
由于多种原因,这行不通。您正在尝试解码一个数组,因此根本不会调用 DailyCount 中的自定义解码实现(如果要编译的话),因为在顶层,您的 JSON 包含一个对象,而不是一个数组。
但是有一个更简单的解决方案,甚至不需要您自己实现 Decodable。
您可以为您的外部对象创建一个通用的包装器结构,并将其用于您需要的任何负载类型:
struct Wrapper<Payload: Decodable>: Decodable {
var data: Payload
}
然后您可以使用它来解码您的 DailyCount 结构数组:
let reports = try JSONDecoder().decode(Wrapper<[DailyCount]>.self, from: data).data
通过在 JSON解码器上创建扩展可以使这更加透明:
extension JSONDecoder {
func decode<T: Decodable>(payload: T.Type, from data: Data) throws -> T {
try decode(Wrapper<T>.self, from: data).data
}
}
Sven 的回答纯粹而优雅,但如果我没有指出还有一种愚蠢但简单的方法,那就是我的失职:根本不使用 Codable 的垃圾箱-潜入 "data"
。示例:
// preconditions
let json = """
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
"""
let jsonData = json.data(using: .utf8)!
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
// okay, here we go
do {
let dict = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [AnyHashable:Any]
let arr = dict?["data"] as? Array<Any>
let json2 = try JSONSerialization.data(withJSONObject: arr as Any, options: [])
let output = try JSONDecoder().decode([DailyCount].self, from: json2)
print(output) // yep, it's an Array of DailyCount
} catch {
print(error)
}