Декодировать динамическое значение класса с помощью Codable

У меня есть следующие классы:

class AlgoliaLocation: Codable {

    var id: String
    var address: String?
    var otherInfo: String?
}

struct AlgoliaHit<T: AlgoliaLocation>: Codable {
    var highlightResult: [T.CodingKeys : [AlgoliaHighlightResult]]
    var coordintates: [AlgoliaCoordinate]

    enum CodingKeys: String, CodingKey {
        case highlightResult = "_highlightResult"
        case coordinates = "_geoloc"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let temp = try? container.decode([T.CodingKeys : AlgoliaHighlightResult].self,
                                                   forKey: .highlightResult) {
        var highlightResult = [T.CodingKeys : [AlgoliaHighlightResult]]()
        for (key, value) in temp {
            highlightResult[key] = [value]
        }
        self.highlightResult = highlightResult
    } else {
        highlightResult = try container.decode([ T.CodingKeys : [AlgoliaHighlightResult]].self,
                                                forKey: .highlightResult)
    }
}

Я застрял в декодировании значения highlightResult, потому что значение ключа кодирования может быть либо массивом, как определено в модели класса AlgoliaHit, либо непосредственно объектом типа AlgoliaHighlightResult. Таким образом, каждый ключ из AlgoliaLocation.CodingKeys может иметь тип [AlgoliaHighlightResult] или AlgoliaHighlightResult, и мне нужен способ перебора каждого динамического ключа при декодировании и сопоставлении значения с массивом, когда он не является массивом. Я пытался декодировать все как значения массива и все как значения объекта, но они чередуются, и ключ может быть одним из них (массивом или объектом). Благодарю вас! Если что-то неясно, это то, что я пытаюсь сопоставить: Алголия JSON.


person Bogdan    schedule 16.03.2020    source источник


Ответы (2)


Вы можете справиться с методами инициализации (от декодера: декодера)

    if let objHits =  try values.decodeIfPresent(Hits.self, forKey: .hits) {
        hits = [objHits]
    } else {
        hits = try values.decodeIfPresent([Hits].self, forKey: .hits)
    }

Я буду следовать приведенному ниже фрагменту кода, чтобы правильно его проанализировать.

import Foundation
struct algolia : Codable {
let hits : [Hits]?
let page : Int?
let nbHits : Int?
let nbPages : Int?
let hitsPerPage : Int?
let processingTimeMS : Int?
let query : String?
let params : String?

enum CodingKeys: String, CodingKey {

    case hits = "hits"
    case page = "page"
    case nbHits = "nbHits"
    case nbPages = "nbPages"
    case hitsPerPage = "hitsPerPage"
    case processingTimeMS = "processingTimeMS"
    case query = "query"
    case params = "params"
}

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    if let objHits =  try values.decodeIfPresent(Hits.self, forKey: .hits) {
        hits = [objHits]
    } else {
        hits = try values.decodeIfPresent([Hits].self, forKey: .hits)
    }
    page = try values.decodeIfPresent(Int.self, forKey: .page)
    nbHits = try values.decodeIfPresent(Int.self, forKey: .nbHits)
    nbPages = try values.decodeIfPresent(Int.self, forKey: .nbPages)
    hitsPerPage = try values.decodeIfPresent(Int.self, forKey: .hitsPerPage)
    processingTimeMS = try values.decodeIfPresent(Int.self, forKey: .processingTimeMS)
    query = try values.decodeIfPresent(String.self, forKey: .query)
    params = try values.decodeIfPresent(String.self, forKey: .params)
}}
struct Hits : Codable {
let firstname : String?
let lastname : String?
let objectID : String?
let _highlightResult : _highlightResult?

enum CodingKeys: String, CodingKey {

    case firstname = "firstname"
    case lastname = "lastname"
    case objectID = "objectID"
    case _highlightResult = "_highlightResult"
}

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    firstname = try values.decodeIfPresent(String.self, forKey: .firstname)
    lastname = try values.decodeIfPresent(String.self, forKey: .lastname)
    objectID = try values.decodeIfPresent(String.self, forKey: .objectID)
    _highlightResult = try values.decodeIfPresent(_highlightResult.self, forKey: ._highlightResult)
}}
struct Firstname : Codable {
let value : String?
let matchLevel : String?

enum CodingKeys: String, CodingKey {

    case value = "value"
    case matchLevel = "matchLevel"
}

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    value = try values.decodeIfPresent(String.self, forKey: .value)
    matchLevel = try values.decodeIfPresent(String.self, forKey: .matchLevel)
}}
struct _highlightResult : Codable {
let firstname : Firstname?
let lastname : Lastname?
let company : Company?
enum CodingKeys: String, CodingKey {
    case firstname = "firstname"
    case lastname = "lastname"
    case company = "company"
}
init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    firstname = try values.decodeIfPresent(Firstname.self, forKey: .firstname)
    lastname = try values.decodeIfPresent(Lastname.self, forKey: .lastname)
    company = try values.decodeIfPresent(Company.self, forKey: .company)
}}

В вашем контроллере представления используйте приведенный ниже код

    func jsonToCodable<T: Codable>(json: [String: Any], codable: T.Type) -> T? {
    let decoder = JSONDecoder()
    do {
        let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
        let codable = try decoder.decode(codable, from: data)
        return codable

    } catch {
        print("*/ json failed */")
        print(error)
        //print(error.localizedDescription)
        print(json)
    }
    return nil
}
 if let algoliaObject = jsonToCodable(json: jsonictionary, codable: algolia.self) {// using optional chaining access _highlightResult }
person navroz    schedule 16.03.2020
comment
Проблема на моей стороне и ссылка на ваш пример, на моей стороне свойства, которые у вас есть (имя, фамилия, компания), будут выводиться динамически в зависимости от различных моделей, поэтому на данный момент у меня есть AlgoliaHit‹T: AlgoliaLocation›, потому что в настоящее время я пытаюсь для сопоставления объекта типа AlgoliaLocation. поэтому ключи будут взяты из указанного универсального типа, но проблема в том, что свойства (например, то, что у вас есть: имя, фамилия, компания) могут быть либо массивом имени, либо именем, как в вашем примере, я не уверен, как проверьте это, когда ключи берутся динамически. - person Bogdan; 16.03.2020
comment
можете ли вы добавить все типы JSON, которые вы ожидаете? - person navroz; 16.03.2020

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

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let highlightResultContainer = try container.nestedContainer(keyedBy: T.CodingKeys.self, forKey: .highlightResult)

    var highlightResult = [T.CodingKeys : [AlgoliaHighlightResult]]()

    for highlightResultKey in highlightResultContainer.allKeys {
        if let value = try? highlightResultContainer.decode(AlgoliaHighlightResult.self,
                                                            forKey: highlightResultKey) {
            highlightResult[highlightResultKey] = [value]
        } else {
            let value = try highlightResultContainer.decode([AlgoliaHighlightResult].self,
                                                            forKey: highlightResultKey)
            highlightResult[highlightResultKey] = value
        }
    }
    self.highlightResult = highlightResult
}
person Bogdan    schedule 16.03.2020