Как обрабатывать динамические ключи из ответа JSON с помощью jsonDecoder? - PullRequest
0 голосов
/ 25 февраля 2019

Как мне обработать этот JSON и проанализировать этот JSON с помощью декодера в Swift 4?Я пытался несколько раз, но не получилось.Я не понимаю, как обращаться с этим форматом JSON.

[
  {
    products: {
      id: 69,
      name: "test",
      des: "Hi this is a test",
      sort_des: "this is a test category",
    },
    documents: {
      0: {
        id: 1,
        name: "105gg_1992uu",
        citation: "This is citation for 105gg_1992uu",
        file: "105gg_1992uu.pdf",

       created_at: "2019-01-25 09:07:09",
        category_id: 69,
      },
      1: {
        id: 2,
        name: "96tt-1997tt",
        citation: "This is citation for 96tt-1997tt",
        file: "96tt-1997tt.pdf",
        created_at: "2019-01-25 09:07:09",
        category_id: 69,
      },

    },

  }
]

Я попробовал следующий код.Это мой класс модели.

struct GenericCodingKeys: CodingKey {
    var stringValue: String
    var intValue: Int?
    init?(stringValue: String) {
        self.stringValue = stringValue
    }
    init?(intValue: Int) {
        self.intValue = intValue
        self.stringValue = "\(intValue)"
    }
}

struct Model : Codable {

  struct Documents : Codable {
        var id : Int?
        var name : String?
        var citation : String?
        var file : String?
        var created_at : String?
        var category_id : Int?## Heading ##

     private enum CodingKeys : String, CodingKey{
            case id = "id"
            case name =  "name"
            case citation = "citation"
            case file = "file"
            case created_at = "created_at"
            case category_id = "category_id"
      }
   }

   struct Products : Codable {
        var id : Int?
        var name : String?
        var des : String?
        var sort_des : String?

      private enum CodingKeys: String, CodingKey{
            case id = "id"
            case name = "name"
            case des = "des"
            case sort_des = "sort_des"
      }
  }

    var products : Products?
    var documents : [Documents]?
    private enum CodingKeys : String, CodingKey{
        case products
        case documents
    }
    init(from decoder: Decoder) throws {
        self.documents = [Documents]()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.products = try container.decode(Products.self, forKey: .products)
        let documents = try container.nestedContainer(keyedBy: GenericCodingKeys.self, forKey: .documents)
        for doc in documents.allKeys{
            let docEach = try documents.decode(Documents.self, forKey: doc)
            self.documents?.append(docEach)
        }
    }
}

Это мои данные выборки из этой функции JSON

   class LatestStatuesVC: UIViewController,UITableViewDelegate,UITableViewDataSource {

    @IBOutlet var tableView: UITableView!
    var caseData : [Model]?
    var model : Model?
    var countCaseData = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        downloadAllData()
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return countCaseData
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: ReuseIdentifiers.cellLatestStatues, for: indexPath) as! LatestStatuesTableCell
        return cell
    }

    //MARK: Download all documents into internal directory
    func downloadAllData(){
        let url = URL(string: URLString.urlForGetDocuments)
        URLSession.shared.dataTask(with: url!) { (data, response, err) in
            DispatchQueue.main.async {
                do{
                    if err == nil {
                        let products = try JSONDecoder().decode(Model.Products.self, from: data!)
                        let documentAll = try JSONDecoder().decode([Model.Documents].self, from: data!)
                        print(products.name as Any)
                        self.countCaseData = documentAll.count
                        for doc in documentAll{
                            print(doc.name as Any)
                            print(doc.citation as Any)
                        }
                    }
                }
                catch let err{
                    print(err)
                }
            }
        }.resume()
    }

}

Я получаю эту ошибку для этого кода.

   typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))

1 Ответ

0 голосов
/ 25 февраля 2019

Ошибка ясно говорит о том, что корневой объект JSON является массивом, но вы пытаетесь декодировать словарь.

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

struct Model : Decodable {
    let products : Product
    let documents : [Document]

    enum CodingKeys: String, CodingKey { case products, documents }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        products = try container.decode(Product.self, forKey: .products)
        let documentData = try container.decode([String:Document].self, forKey: .documents)
        documents = documentData.values.sorted(by: {$0.id < $1.id})
    }
}

struct Product: Decodable {
    let id : Int
    let name, description, sortDescription : String
    let type : String
}

struct Document: Decodable {
    let id, categoryId : Int
    let name, citation, file : String
    let createdAt : Date
}

Затем декодируйте JSON (при условии, что data представляет JSON как Data), значения createdAt декодируются как Date

let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
    let modelArray = try decoder.decode([Model].self, from: data)
    for model in modelArray {
        print("products:",model.products)
        print("documents:",model.documents)
    }

} catch { print(error) }

Стратегия декодирования ключа convertFromSnakeCase преобразует все snake_cased ключи в camelCased struct членов без указания каких-либо CodingKeys.

...