valueNotFound: не удается получить контейнер для декодирования с ключом, вместо которого найдено значение null

#swift #firebase #firebase-realtime-database

# #swift #firebase #firebase-realtime-database

Вопрос:

Мой проект включает динамические данные, которые изменяются при смене контроллеров и т. Д., Поэтому в какой-то момент мои данные могут быть:

 [
   {
      "callDescription":"TEST 16/12",
      "callDuration":"5-8 Minutes",
      "callID":0,
      "callMade":false,
      "callMade_dateTime":"false_1608151560.0",
      "dateTime":1608044666,
      "type":"Breakfast Call"
   },
     {
      "callDescription":"TEST 16/12",
      "callDuration":"5-8 Minutes",
      "callID":0,
      "callMade":false,
      "callMade_dateTime":"false_1608151560.0",
      "dateTime":1608044666,
      "type":"Breakfast Call"
   },
]
 

Затем выполняется фрагмент кода, и мои данные теперь

 [
   {
      "callDescription":"TEST 16/12",
      "callDuration":"5-8 Minutes",
      "callID":0,
      "callMade":false,
      "callMade_dateTime":"false_1608151560.0",
      "dateTime":1608044666,
      "type":"Breakfast Call"
   },
     null
]
 

Что вызывает ошибку valueNotFound при повторном запросе данных.

Каков наилучший способ пропустить / обработать любые индексы, которые имеют значение null?

Вот мой код API:

 class Service {
    static let shared = Service()
    let BASE_URL = "https://url.com"
    
    func fetchClient(completion: @escaping ([Calls]) -> ()) {

    guard let url = URL(string: BASE_URL) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, error) in

        // handle error
        if let error = error {
            print("Failed to fetch data with error: ", error)
            return
        }

        guard let data = data else {return}

        do {
            let myDecoder = JSONDecoder()
            let calls = try myDecoder.decode([Calls].self, from: data)
            completion(calls)
        } catch let error {
            print("Failed to create JSON with error: ", error)
        }
    }.resume()
}
 

Calls Модель:

 struct Calls: Decodable  {
    let callDescription, callDuration, callMade_dateTime: String
    let callID: Int
    let dateTime: Date
    let callMade: Bool
    let type: String
}
 

Комментарии:

1. У вас есть словарь, а не массив. Я не знаю, что такое вызовы, но, возможно, вы можете использовать .decode([String: Calls].self,...

2. Обновил q, чтобы включить мою модель. String: Calls к сожалению, не сработает.

3. Я не понимаю, как этот тип (вызовы) может соответствовать json в вашем вопросе

4. Я не понимаю, как ваше декодирование будет работать как таковое с образцом JSON. Это не имеет смысла. Вы пропускаете / скрываете слишком много информации, и нам сложнее определить, что не так.

5. Создайте понятный пример, но я думаю, что [Calls].self так и должно быть [Calls?].self . Тогда, если вам не нужны опции, просто flatMap() по значению.

Ответ №1:

Быстрое решение:

 let calls = try myDecoder.decode([Calls].self, from: data)
completion(calls)
 

=>

 let calls = try myDecoder.decode([Calls?].self, from: data)
completion(calls.compactMap{ $0 })
 

Давайте упростим пример (я начал писать ответ до того, как вы написали настоящий рабочий JSON) :

 struct Custom: Codable {
    let data: String
}

let jsonString = """
[{"data": "Hello"}, {"data": "world"}]
"""

let jsonString2 = """
    [{"data": "Hello"}, null, {"data": "world"}]
"""
 

Итак, некоторые значения могут быть null внутри вашего JSON. Вот где мы можем использовать Optional .

 func test(json: String) {
    do {
        print("Testing with [Custom].self: (json)")
        let customs = try JSONDecoder().decode([Custom].self, from: json.data(using: .utf8)!)
        print("Result: (customs)")
    } catch {
        print("Error: (error)")
    }
}

func test2(json: String) {
    do {
        print("Testing with [Custom?].self: (json)")
        let customs = try JSONDecoder().decode([Custom?].self, from: json.data(using: .utf8)!)
        print("Result with optionals: (customs)")
        let unwrapped = customs.compactMap { $0 }
        print("Result unwrapped: (unwrapped)")
    } catch {
        print("Error: (error)")
    }
}
 
 test(json: jsonString)
test(json: jsonString2)
test2(json: jsonString)
test2(json: jsonString2)
 

Вывод:



gt;Testing with [Custom].self: [{"data": "Hello"}, {"data": "world"}]


gt;Result: [Custom(data: "Hello"), .Custom(data: "world")]


gt;Testing with [Custom].self: [{"data": "Hello"}, null, {"data": "world"}]


gt;Error: valueNotFound(Swift.KeyedDecodingContainer<.Custom.CodingKeys>, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 1", intValue: 1)], debugDescription: "Cannot get keyed decoding container -- found null value instead.", underlyingError: nil))


gt;Testing with [Custom?].self: [{"data": "Hello"}, {"data": "world"}]


gt;Result with optionals: [Optional(.Custom(data: "Hello")), Optional(.Custom(data: "world"))]


gt;Result unwrapped: [.Custom(data: "Hello"), .Custom(data: "world")]


gt;Testing with [Custom?].self: [{"data": "Hello"}, null, {"data": "world"}]


gt;Result with optionals: [Optional(.Custom(data: "Hello")), nil, Optional(.Custom(data: "world"))]


gt;Result unwrapped: [.Custom(data: "Hello"), .Custom(data: "world")]