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

时间:2018-06-07 07:42:30

标签: json swift codable

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

所以我可以猜测是因为第一部分数据会给出错误,但是应该省略它还是用剩下的数据生成数据?

2 个答案:

答案 0 :(得分:0)

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

NOT 声明选项:

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

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

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

此外,可编码结构的实现

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

请注意:

    {li> lastUpdatedescription未在VportSensor中使用。
  • 根据我的回答,没有必要TemperatureSensor ...

提示

在处理日期时(例如ts),您应该直接将其声明为Date而不是String,然后设置方便的dateDecodingStrategy。在您的情况下,它应该是自定义的,您可以在this answer中找到如何执行此操作。

实现:

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

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

答案 1 :(得分: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都有一些缺失的键时,此技术很有用。