-1

How do I handle this JSON and parse this JSON using decoder in Swift 4? I tried several times but failed. I don't understand how to handle this JSON format.

[
  {
    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,
      },

    },

  }
]

I tried the following code. This is my model class.

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

This is my fetch data from that JSON function

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

}

I get this error for this code.

   typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
E_net4
  • 27,810
  • 13
  • 101
  • 139
Avijit Mondal
  • 59
  • 1
  • 9
  • Do you receive any error message? What result do you get? – Magnas Feb 25 '19 at 12:41
  • yes. I get "typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil)) " this error message. – Avijit Mondal Feb 25 '19 at 12:43
  • **Read** the JSON – which is in fact invalid JSON – , it starts with `[` so the root object is clearly an array. That's what the error message says. – vadian Feb 25 '19 at 12:48

1 Answers1

1

The error clearly says that the root object of the JSON is an array but you try to decode a dictionary.

Basically your structs are too complicated. If you want to have documents as an array by getting rid of the numeric dictionary keys just write a custom initializer in the root (Model) struct which decodes documents as dictionary and takes the values sorted by id as the documents array.

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
}

Then decode the JSON (assuming data represents the JSON as Data), the createdAt values are decoded as 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) }

The convertFromSnakeCase key decoding strategy converts all snake_cased keys to camelCased struct members without specifying any CodingKeys.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Why you use "Locale". Can you elaborate your code in just few line ? @vadian – Avijit Mondal Feb 25 '19 at 13:35
  • btw thanks for your comment. I think it help me out.but when I compile your code there show this error: "valueNotFound(Swift.KeyedDecodingContainer(unknown context at 0x10c2d86c8).CodingKeys>, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "documents", intValue: nil), _DictionaryCodingKey(stringValue: "3876", intValue: 3876)], debugDescription: "Cannot get keyed decoding container -- found null value instead.", underlyingError: nil)) " – Avijit Mondal Feb 25 '19 at 13:36
  • The fixed locale is used to avoid unexpected behavior while formatting the date. My code is similar to yours but just simpler. – vadian Feb 25 '19 at 13:37
  • but It not run successfully with your code.Above error has come. – Avijit Mondal Feb 25 '19 at 13:40
  • My answer is for the JSON in the question. If you want a working solution post the **real** JSON – vadian Feb 25 '19 at 13:40
  • actually I working on a project currently and I want to fetch all document file name exactly and I got that type of json format from the api backend. It not a array properly, that's why I got problem with decoder. I want to "get" that type of json and perse it. And print it. Is there any other procedure to fetch that type of json format quickly. If you have a tutorial for this then suggest me please. @vadian – Avijit Mondal Feb 25 '19 at 14:05
  • Parsing JSON with `Decodable` is pretty fast and pretty easy, there are only 2 collection types and 4 values types. I recommend to read [this article](https://developer.apple.com/swift/blog/?id=37) in the Swift blog. – vadian Feb 25 '19 at 14:22