Mam następujące zajęcia:

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)
    }
}

Utknąłem dekodowanie wartości highlightResult, ponieważ wartość klucza kodowania może być tablicą, taką jak zdefiniowano w modelu klasy AlgoliaHit lub bezpośrednio obiektem typu AlgoliaHighlightResult. Tak więc każdy klucz z AlgoliaLocation.CodingKeys może być typu [AlgoliaHighlightResult] lub AlgoliaHighlightResult i potrzebuję sposobu, aby przejść przez każdy klucz dynamiczny podczas dekodowania i odwzorować wartość na tablicę, gdy jest nie tablica. Próbowałem zdekodować wszystkie jako wartości tablicowe i wszystkie jako wartości obiektów, ale są one naprzemienne, a klucz może być jednym z nich (tablica lub obiekt). Dziękuję Ci! W przypadku, gdy coś nie jest jasne, to próbuję zmapować: Algolia JSON .

0
Bogdan 16 marzec 2020, 13:28

2 odpowiedzi

Najlepsza odpowiedź

Więc udało mi się to osiągnąć za pomocą zagnieżdżonego kontenera, którego mogłem użyć do iteracji po kluczach dynamicznych i sprawdzenia każdego typu:

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
}
0
Bogdan 16 marzec 2020, 11:24

Możesz sobie z tym poradzić w metodach init (z decoder: Decoder)

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

Podążę za poniższym fragmentem kodu, aby go poprawnie przeanalizować.

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)
}}

W widoku kontrolera użyj poniższego kodu

    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 }
1
navroz 16 marzec 2020, 11:00