Декодирование динамической структуры JSON в Swift 4 - PullRequest
0 голосов
/ 03 января 2019

У меня есть следующая проблема, которую я не знаю, как решить.

Мой JSON ответ может выглядеть так:

{ 
  "data": {
      "id": 7,
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDY1MTU0NDMsImRhdGEiOiJ2bGFkVGVzdCIsImlhdCI6MTU0NjUwODI0M30.uwuPhlnchgBG4E8IvHvK4bB1Yj-TNDgmi7wUAiKmoVo"
   },
  "error": null
}

Или вот так:

{
 "data": [{
     "id": 12
    }, {
      "id": 2
    }, {
       "id": 5
    }, {
       "id": 7
    }],
 "error": null
}

Короче говоря, данные могут быть как одним объектом, так и массивом. Что у меня есть это:

struct ApiData: Decodable {
    var data: DataObject?
    var error: String?
}

struct DataObject: Decodable {
    var userId: Int?

    enum CodingKeys: String, CodingKey {
        case userId = "id"
    }
}

Это отлично работает для первого варианта использования, но не получится, если данные превратятся в

var data: [DataObject?]

Как мне сделать это динамическим без дублирования кода?

Редактировать: Я так же декодирую объект

 func makeDataTaskWith(with urlRequest: URLRequest, completion: @escaping(_ apiData: ApiData?) -> ()) {
    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)

    session.dataTask(with: urlRequest) {
        (data, response, error) in
        guard let _ = response, let data = data else {return}

        if let responseCode = response as? HTTPURLResponse {
            print("Response has status code: \(responseCode.statusCode)")
        }

        do {
            let retreived = try NetworkManager.shared.decoder.decode(ApiData.self, from: data)
            completion(retreived)
        } catch let decodeError as NSError {
            print("Decoder error: \(decodeError.localizedDescription)\n")
            return
        }
        }.resume()
}

Ответы [ 4 ]

0 голосов
/ 03 января 2019

Используя мощность generic, все просто, как показано ниже:

struct ApiData<T: Decodable>: Decodable {
    var data: T?
    var error: String?
}

struct DataObject: Decodable {
    private var id: Int?

    var userId:Int? {
        return id
    }
}

Используйте

if let obj = try? NetworkManager.shared.decoder.decode(ApiData<DataObject>.self, from: data) {
    //Do somthing
} else if let array = try NetworkManager.shared.decoder.decode(ApiData<[DataObject]>.self, from: data) {
    // Do somthing
}
0 голосов
/ 03 января 2019

Можно попробовать

struct Root: Codable {
    let data: DataUnion
    let error: String?
}

enum DataUnion: Codable {
    case dataClass(DataClass)
    case datumArray([Datum])

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode([Datum].self) {
            self = .datumArray(x)
            return
        }
        if let x = try? container.decode(DataClass.self) {
            self = .dataClass(x)
            return
        }
        throw DecodingError.typeMismatch(DataUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for DataUnion"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .dataClass(let x):
            try container.encode(x)
        case .datumArray(let x):
            try container.encode(x)
        }
    }
}

struct Datum: Codable {
    let id: Int
}

struct DataClass: Codable {
    let id: Int
    let token: String
}

let res = try? JSONDecoder().decode(Root.self, from:data)
0 голосов
/ 03 января 2019

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

Поскольку token отображается только в одном объекте, свойство объявляется необязательным.

struct ApiData: Decodable {
    let data : [DataObject]
    let error : String?

    private enum CodingKeys : String, CodingKey { case data, error }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            data = try container.decode([DataObject].self, forKey: .data)
        } catch DecodingError.typeMismatch {
            data = [try container.decode(DataObject.self, forKey: .data)]
        }
        error = try container.decodeIfPresent(String.self, forKey: .error)
    }
}


struct DataObject: Decodable {
    let userId : Int
    let token : String?

    private enum CodingKeys: String, CodingKey { case userId = "id", token }
}

Редактировать: Ваш код для получения данных может быть улучшен. Вы должны добавить лучшую обработку ошибок, чтобы возвращать также все возможные ошибки:

func makeDataTaskWith(with urlRequest: URLRequest, completion: @escaping(ApiData?, Error?) -> Void) {
    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)

    session.dataTask(with: urlRequest) {
        (data, response, error) in
        if let error = error { completion(nil, error); return }

        if let responseCode = response as? HTTPURLResponse {
            print("Response has status code: \(responseCode.statusCode)")
        }

        do {
            let retreived = try NetworkManager.shared.decoder.decode(ApiData.self, from: data!)
            completion(retreived, nil)
        } catch {
            print("Decoder error: ", error)
            completion(nil, error)
        }
        }.resume()
}
0 голосов
/ 03 января 2019

Если у вас есть только два возможных результата для ваших данных, можно было бы попытаться проанализировать данные для одного из ожидаемых типов, если вам это не удастся, вы знаете, что данные относятся к другому типу, и затем вы можете обработать их соответствующим образом.

См. это

...