使用 Codable 解码 class 的动态值

Decode dynamic value of class using Codable

我有以下 classes:

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 class 模型中定义的数组,也可以直接是 class 的对象输入 AlgoliaHighlightResult。因此 AlgoliaLocation.CodingKeys 中的每个键都可以是 [AlgoliaHighlightResult]AlgoliaHighlightResult 类型,我需要一种方法在解码时循环遍历每个动态键,并在解码时将值映射到数组不是数组。我试图将所有解码为数组值,并将所有解码为对象值,但它们交替出现,并且键可以是其中之一(数组或对象)。谢谢! 如果不清楚,这就是我要映射的内容: Algolia JSON.

You can handle it init(from decoder: Decoder) methods

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

I will follow, the below code snippet to parse it correctly.

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 }

所以我设法使用嵌套容器来完成此操作,我可以使用它来遍历动态键并检查每种类型:

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
}