我目前正在尝试解码如下所示的 JSON:
{
"result": {
"success": true,
"items": [
{
"timeEntryID": "1",
"start": "1519558200",
"end": "1519563600",
"customerName": "Test-Customer",
"projectName": "Test-Project",
"description": "Entry 1",
},
{
"timeEntryID": "2",
"start": "1519558200",
"end": "1519563600",
"customerName": "Test-Customer",
"projectName": "Test-Project",
"description": "Entry 2",
}
],
"total": "2"
},
"id": "1"
}
这种特定类型的 JSON 的解码过程非常简单。我只需要这样的东西:
struct ResponseKeys: Decodable {
let result: ResultKeys
struct ResultKeys: Decodable {
let success: Bool
let items: [Item]
}
}
现在我面临的问题是服务器的每个响应都具有与上述 JSON 相同的结构,但具有不同的项目类型。所以有时它是 let items: [Item]
但它也可能是 let items: [User]
如果我调用用户端点。
因为如果我只修改 items 数组就为每个端点编写上面的 swift 代码,那将是不必要的代码重复,所以我创建了一个自定义解码器:
enum KimaiAPIResponseKeys: String, CodingKey {
case result
enum KimaiResultKeys: String, CodingKey {
case success
case items
}
}
struct Activity: Codable {
let id: Int
let description: String?
let customerName: String
let projectName: String
let startDateTime: Date
let endDateTime: Date
enum CodingKeys: String, CodingKey {
case id = "timeEntryID"
case description
case customerName
case projectName
case startDateTime = "start"
case endDateTime = "end"
}
}
extension Activity {
init(from decoder: Decoder) throws {
let resultContainer = try decoder.container(keyedBy: KimaiAPIResponseKeys.self)
let itemsContainer = try resultContainer.nestedContainer(keyedBy: KimaiAPIResponseKeys.KimaiResultKeys.self, forKey: .result)
let activityContainer = try itemsContainer.nestedContainer(keyedBy: Activity.CodingKeys.self, forKey: .items)
id = Int(try activityContainer.decode(String.self, forKey: .id))!
description = try activityContainer.decodeIfPresent(String.self, forKey: .description)
customerName = try activityContainer.decode(String.self, forKey: .customerName)
projectName = try activityContainer.decode(String.self, forKey: .projectName)
startDateTime = Date(timeIntervalSince1970: Double(try activityContainer.decode(String.self, forKey: .startDateTime))!)
endDateTime = Date(timeIntervalSince1970: Double(try activityContainer.decode(String.self, forKey: .endDateTime))!)
}
}
如果 "items"
只包含一个对象而不是数组,解码器将完美运行:
{
"result": {
"success": true,
"items":
{
"timeEntryID": "2",
"start": "1519558200",
"end": "1519563600",
"customerName": "Test-Customer",
"projectName": "Test-Project",
"description": "Entry 2",
},
"total": "2"
},
"id": "1"
}
如果 items
是一个数组,我会得到以下错误:
typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [__lldb_expr_151.KimaiAPIResponseKeys.result], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
我只是不知道如何修改我的解码器以处理一组项目。我使用 JSON 的工作和非工作版本创建了一个 Playground 文件。请看一下并尝试一下:Decodable.playground
感谢您的帮助!
最佳答案
我的建议是分别为 items
解码字典
struct Item : Decodable {
enum CodingKeys: String, CodingKey {
case id = "timeEntryID"
case description, customerName, projectName
case startDateTime = "start"
case endDateTime = "end"
}
let id: Int
let startDateTime: Date
let endDateTime: Date
let customerName: String
let projectName: String
let description: String?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = Int(try container.decode(String.self, forKey: .id))!
description = try container.decodeIfPresent(String.self, forKey: .description)
customerName = try container.decode(String.self, forKey: .customerName)
projectName = try container.decode(String.self, forKey: .projectName)
startDateTime = Date(timeIntervalSince1970: Double(try container.decode(String.self, forKey: .startDateTime))!)
endDateTime = Date(timeIntervalSince1970: Double(try container.decode(String.self, forKey: .endDateTime))!)
}
}
并且在 Activity
中使用条件初始化程序,它提供了自己的 do catch
block 。首先,它尝试解码单个项目并将单个项目作为数组分配给属性。如果失败,它会解码一个数组。
enum KimaiAPIResponseKeys: String, CodingKey {
case result, id
enum KimaiResultKeys: String, CodingKey {
case success
case items
}
}
struct Activity: Decodable {
let id: String
let items: [Item]
}
extension Activity {
init(from decoder: Decoder) throws {
let rootContainer = try decoder.container(keyedBy: KimaiAPIResponseKeys.self)
id = try rootContainer.decode(String.self, forKey: .id)
let resultContainer = try rootContainer.nestedContainer(keyedBy: KimaiAPIResponseKeys.KimaiResultKeys.self, forKey: .result)
do {
let item = try resultContainer.decode(Item.self, forKey: .items)
items = [item]
} catch {
items = try resultContainer.decode([Item].self, forKey: .items)
}
}
}
关于json - swift 可编码 : Decode different array of items with same root objects,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48973418/