Расшифровка JSON с кодируемой проблемой.сообщение об ошибке keyNotFound - PullRequest
0 голосов
/ 07 июня 2018

У меня проблема с декодированием 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 голосов
/ 23 апреля 2019

Вы можете легко пропустить ключи, которые не получают от ответа сервера.

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 есть несколько пропущенных ключей.

0 голосов
/ 07 июня 2018

После отслеживания вашей проблемы, я понял, что есть пара проблем, в первую очередь:

Вы НЕ объявляете дополнительные опции:

основываясь на прикрепленном json, кажется, что существуют некоторые свойства, которые не существуют всегда, например:

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

вам потребуется , убедитесь, что объявили любое свойство, которое может быть не получено как необязательное.

Также, Реализация кодируемых структур :

реализованные структуры кажутся не типичными для структуры ответа json, убедитесь, что ваши кодируемые структуры сопоставлены с полученной структурой json.


Обратите внимание, что:

  • lastUpdate и description не используются в VportSensor.
  • Исходя из моего ответа, нет необходимости TemperatureSensor...

Tip:

Когда дело доходит до работы с датами (например, 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 и добавил его в 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)!
...