使用Codable问题解码JSON。 keyNotFound错误消息

问题描述 投票:0回答:2

我有解码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 swift codable
2个回答
0
投票

在追踪你的问题之后,我认为有几个问题,首先:

您没有声明可选项:

基于附加的json,似乎有一些并不总是存在的属性,例如:

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

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

此外,可编码结构的实施:

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


注意:

  • lastUpdatedescription不用于VportSensor
  • 根据我的回答,没有必要TemperatureSensor ...

小费:

当涉及到使用日期(例如ts)时,你应该直接将它声明为Date而不是String,然后设置方便的dateDecodingStrategy。在你的情况下,它应该是一个自定义的,你可以找到如何在this answer中做到这一点。


Implementation:

基于以上描述,有完整的实现:

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响应并将其添加到Data对象中:

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

0
投票

您可以轻松跳过那些未从服务器响应中获取的密钥。

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都有一些缺失键时,此技术非常有用。

© www.soinside.com 2019 - 2024. All rights reserved.