Swift - создание базовой декодируемой структуры с помощью Generics - PullRequest
0 голосов
/ 27 мая 2020

Итак, я пытаюсь построить модель, которая будет отвечать за URLRequest s и синтаксический анализ Decodable s. Ответ, поступающий от сервера, имеет ту же форму в самом высоком диапазоне, включая ключи status, page_count и results.

results значения меняются относительно request, page_count является необязательным, а status - это просто String, указывающее, было ли request успешным или нет.

Я пытался реализовать сам Generics в method и базу Decodable struct с именем APIResponse, а ниже - пример только одной конечной точки с именем extList. Код компилируется, однако во время выполнения он выдает

Thread 7: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "results", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"results\", intValue: nil) (\"results\").", underlyingError: nil))

в строке json = try! JSONDecoder().decode(APIResponse<T>.self, from: data)

class NetworkManager {

    typealias completion<T: Decodable> = (Result<APIResponse<T>, Error>)->()

    class func make<T: Decodable>(of type: T.Type,
                                  request: API,
                                  completion: @escaping completion<T>){

        let session = URLSession.shared
        var req = URLRequest(url: request.url)
        req.httpMethod = request.method
        req.httpBody = request.body

        session.dataTask(with: req) { (data, response, error) in
            if let error = error {
                completion(.failure(error))
                return
            }
            var json: APIResponse<T>
            if let data = data,
               let response = response as? HTTPURLResponse?,
               response?.statusCode == 200 {
                switch request {
                case .extList:
                    json = try! JSONDecoder().decode(APIResponse<T>.self, from: data)
                default:
                    return
                }
                completion(.success(json))
            }
        }.resume()
    }
}

Вот база struct

struct APIResponse<T: Decodable>: Decodable {
    var status: String
    var page_count: Int?
    var results: T
}

Вот ответ, который должен заполнить ключ results в APIResponse для этой конечной точки.

struct UserResponse: Decodable {
    var name: String
    var extens: Int
}

Я делаю свой запрос как NetworkManager.make(of: [UserResponse].self, request: .extList) { (result) in ; return }, и он работает, когда я отбрасываю тип Response generi c в APIResponse напрямую с Array<UserResponse>.

По запросу, образец json Я пытаюсь декодировать

{
    "status": "ok-ext_list",
    "page_count": 1,
    "results": [
        {
            "name": "some name",
            "extens": 249
        },
        {
            "name": "some other name",
            "extens": 245
        }
    ]
}

Есть идеи, как это исправить?

МИНИМАЛЬНЫЙ ВОСПРОИЗВОДИМЫЙ ПРИМЕР

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

JSON s

import Foundation

var extListJSON : Data {
    return try! JSONSerialization.data(withJSONObject: [
        "status": "ok_ext-list",
        "page_count": 1,
        "results": [
            [
                "name": "some name",
                "extens": 256
            ],
            [
                "name": "some other name",
                "extens": 262
            ]
        ]
    ], options: .fragmentsAllowed)
}

var extListString: Data {
    return """
        {
            "status": "ok-ext_list",
            "page_count": 1,
            "results": [
                {
                    "name": "some name",
                    "extens": 249
                },
                {
                    "name": "some other name",
                    "extens": 245
                }
            ]
        }
    """.data(using: .utf8)!
}

Менеджер и служба

enum Service {
    case extList
}

class NetworkManager {

    typealias completion<T: Decodable> = (APIResponse<T>) -> ()

    class func make<T: Decodable>(of type: T.Type, request: Service, completion: completion<T>) {

        let data = extListString
        let json: APIResponse<T>

        switch request {
        case .extList:
            json = try! JSONDecoder().decode(APIResponse<T>.self, from: data)
        }
        completion(json)
    }
}

Декодируемые

struct APIResponse<T: Decodable>: Decodable {
    var status: String
    var page_count: Int?
    var results: T
}

struct UserResponse: Decodable {
    var name: String
    var extens: Int
}

Наконец вызов метода

NetworkManager.make(of: [UserResponse].self, request: .extList) { (result) in
    dump(result)
}

Опять же, я понятия не имею, почему это работает. Я просто удалил сетевую часть, и она начала работать. Напоминаем, что мой исходный код тоже работает, если я просто использую отдельный Decodable для каждого request - без использования Generic struct -. Generic make(:_) тоже работает нормально.

...