[英]Decoding JSON with Codable issue. keyNotFound error message
我在解码JSON时遇到问题。 我试图用解码我的JSON
let temp = try JSONDecoder().decode([LastTemperatureResponse].self, from: data)
。
我的可Codable
结构如下:
struct LastTemperatureResponseElement: Codable {
let measurement: Measurement
}
struct Measurement: Codable {
let ts: String
let sensors: [VportSensor]
}
struct VportSensor: TemperatureSensor, Codable {
var lastUpdate: String!
let address, description: String
let status: String
let temperature: Double
}
好吧,如果我尝试解码JSON,则会收到很明显的错误消息
keyNotFound(CodingKeys(stringValue: "status", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "measurement", intValue: nil), CodingKeys(stringValue: "sensors", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"status\", intValue: nil) (\"status\").", underlyingError: nil))
但请看一下我的JSON
[
{
"type": "temperatures",
"ts": "2017-11-08T16:43:59.558Z",
"source": "thermo-king",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2017-11-08T16:43:18.000Z",
"sensors": [
{
"address": "t1",
"description": "LFTest1",
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
},
{
"address": "t2",
"description": "LFTest2",
"setpoints": [
{
"address": "s2",
"name": "LFSTest2"
}
]
},
{
"address": "t3",
"description": "LFTest3",
"setpoints": [
{
"address": "s3",
"name": "LFSTest3"
}
]
},
{
"address": "t4",
"description": "LFTest4"
},
{
"address": "t5",
"description": "LFTest5"
},
{
"address": "t6",
"description": "LFTest6"
}
],
"sensor": {
"address": "t1",
"name": "LFTest1"
},
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:05:38.962Z",
"source": "1-wire",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:05:31.000Z",
"sensors": [
{
"address": "2839A5B104000004",
"description": "1-wire #1",
"status": "ok",
"temperature": 24.8
},
{
"address": "28EFBAB104000061",
"description": "1-wire #3",
"status": "ok",
"temperature": 24.5
},
{
"address": "2845F6B504000034",
"description": "1-wire #2",
"status": "ok",
"temperature": 24.5
}
],
"sensor": {
"address": "2839A5B104000004",
"name": "1-wire #1",
"status": "ok"
},
"temperature": 24.8
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:11:50.030Z",
"source": "vport",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:11:47.000Z",
"sensors": [
{
"address": "1036040010",
"description": "Vport 1-wire",
"status": "high",
"temperature": 26
}
],
"sensor": {
"address": "1036040010",
"name": "Vport 1-wire",
"status": "high"
},
"temperature": 26
}
}
]
因此,我可以猜测这是由于数据的第一部分而产生了错误,但是是否应该将其省略,而将其余部分生成的数据呢?
跟踪您的问题后,我发现有几个问题,首先:
您不是在声明可选的:
基于附加的json,似乎有些属性并不总是存在的,例如:
status
=> VportSensor
。 temperature
=> Measurement
。 temperature
=> VportSensor
。 temperature
=> setpoints
。 您需要确保将可能不会收到的任何属性声明为可选属性。
另外,可编码结构的实现 :
实现的结构似乎不是 json响应结构所特有的,请确保声明您的可编码结构与接收到的json结构匹配。
注意:
VportSensor
中未使用lastUpdate
和description
。 TemperatureSensor
... 小费:
在处理日期(例如ts
)时,应将其直接声明为Date
而不是String
,然后设置方便的dateDecodingStrategy
。 在您的情况下,它应该是自定义的,您可以在此答案中找到方法。
根据上面的描述,有完整的实现:
struct Main: Codable {
let type: String
let ts: Date
let source: String
let unit: Unit
let measurement: Measurement
}
struct Unit: Codable {
var number: String
}
struct Measurement: Codable {
let ts: String
let sensors: [VportSensor]
let sensor: VportSensor
let temperature: Double?
}
struct LastTemperatureResponseElement: Codable {
let measurement: Measurement
}
struct VportSensor: Codable {
//let lastUpdate: String!
//let description: String
let address: String
let name: String?
let status: String?
let temperature: Double?
let setpoints: [Setpoint]?
}
struct Setpoint: Codable {
let address: String
let name: String
}
// this part from the mentioned answer for creating custom `dateDecodingStrategy`:
enum DateError: String, Error {
case invalidDate
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
throw DateError.invalidDate
})
输出:
let decoder = JSONDecoder()
do {
let temp = try decoder.decode([Main].self, from: json)
// here we go, `temp` is an array of main object of the json
} catch {
print(error)
}
如果您想知道json
中的json
是什么
let temp = try decoder.decode([Main].self, from: json)
我只是将附件的json响应添加到Data
Object中:
let json = """
[
{
"type": "temperatures",
"ts": "2017-11-08T16:43:59.558Z",
"source": "thermo-king",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2017-11-08T16:43:18.000Z",
"sensors": [
{
"address": "t1",
"description": "LFTest1",
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
},
{
"address": "t2",
"description": "LFTest2",
"setpoints": [
{
"address": "s2",
"name": "LFSTest2"
}
]
},
{
"address": "t3",
"description": "LFTest3",
"setpoints": [
{
"address": "s3",
"name": "LFSTest3"
}
]
},
{
"address": "t4",
"description": "LFTest4"
},
{
"address": "t5",
"description": "LFTest5"
},
{
"address": "t6",
"description": "LFTest6"
}
],
"sensor": {
"address": "t1",
"name": "LFTest1"
},
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:05:38.962Z",
"source": "1-wire",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:05:31.000Z",
"sensors": [
{
"address": "2839A5B104000004",
"description": "1-wire #1",
"status": "ok",
"temperature": 24.8
},
{
"address": "28EFBAB104000061",
"description": "1-wire #3",
"status": "ok",
"temperature": 24.5
},
{
"address": "2845F6B504000034",
"description": "1-wire #2",
"status": "ok",
"temperature": 24.5
}
],
"sensor": {
"address": "2839A5B104000004",
"name": "1-wire #1",
"status": "ok"
},
"temperature": 24.8
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:11:50.030Z",
"source": "vport",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:11:47.000Z",
"sensors": [
{
"address": "1036040010",
"description": "Vport 1-wire",
"status": "high",
"temperature": 26
}
],
"sensor": {
"address": "1036040010",
"name": "Vport 1-wire",
"status": "high"
},
"temperature": 26
}
}
]
""".data(using: .utf8)!
您可以轻松地跳过服务器响应中未提供的键。
Example JSON response is:
{
"isValid": false,
"pendingAttempts": 2
}
在这个json响应中,缺少“ id”字段,并且在我们的代码中我们已经声明了它。 因此,我们可以通过以下代码轻松跳过它。
//Code example
struct ResponseModel: Codable {
var id: String? //misng in response
var isValid: Bool?
var token: String?
//initializer
init(id: String?, isValid: Bool?, token: String?) {
self.id = id
self.isValid = isValid
self.token = token
}
//definging the coding keys
enum ResponseModelCodingKeys: String, CodingKey {
//The right hand side keys should be same as of json response keys
case id = "id"
case isValid = "isValid"
case token = "token"
}
//decoding initializer
init(from decoder: Decoder) throws {
var id: String?
var isValid: Bool?
var token: String?
let container = try decoder.container(keyedBy: ResponseModelCodingKeys.self) // defining our (keyed) container
do {
//if found then map
id = try container.decode(String.self, forKey: .id)
}
catch {
//not found then just set the default value
/******** This case will be executed **********/
id = ""
}
do {
//if found then map
isValid = try container.decode(Bool.self, forKey: .isValid)
}
catch {
//not found then just set the default value
isValid = false
}
do {
//if found then map
token = try container.decode(String.self, forKey: .token)
}
catch {
//not found then just set the default value
token = ""
}
//Initializing the model
self.init(id: id, isValid: isValid, token: token)
}
}
当我们对多个API有共同的响应并且每个API都有一些缺失的键时,此技术很有用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.