首页 > 解决方案 > 使用 Codable 问题解码 JSON。keyNotFound 错误信息

问题描述

我在解码 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
    }
  }
]

所以我可以猜测这是因为数据的第一部分而给出的错误,但是应该省略它并与其余部分一起生成数据吗?

标签: jsonswiftcodable

解决方案


跟踪您的问题后,我认为有几个问题,首先:

没有声明选项:

根据所附的json,似乎有一些属性并不总是存在,例如:

  • status=> VportSensor
  • temperature=> Measurement
  • temperature=> VportSensor
  • temperature=> setpoints

您需要确保将任何可能未收到的属性声明为可选。

此外,可编码结构的实现

实现的结构似乎不是典型的 json 响应结构,请确保声明您的可编码结构与接收到的 json 结构匹配。


注意:

  • lastUpdate并且description不用于VportSensor.
  • 根据我的回答,没有必要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什么

let temp = try decoder.decode([Main].self, from: json)

我只是将附加的 json 响应添加到DataObject 中:

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)!

推荐阅读