Search code examples
jsonswiftswift4codabledecodable

Extracting data from JSON array with swift Codable


I have a JSON response like this:

enter image description here

I have currently designed my decodable struct to be as follows:

    struct PortfolioResponseModel: Decodable {
    var dataset: Dataset

    struct Dataset: Decodable {
        var data: Array<PortfolioData> //I cannot use [Any] here...

        struct PortfolioData: Decodable {
            //how to extract this data ?
        }
    }
   }

The question is, how do I extract the data inside the array, which can have a value Double or String.

Here is the sample string to make this work on playground:

   let myJSONArray =
   """
   {
   "dataset": {
   "data": [
    [
   "2018-01-19",
   181.29
   ],
   [
   "2018-01-18",
   179.8
   ],
   [
   "2018-01-17",
   177.6
   ],
   [
   "2018-01-16",
   178.39
   ]
   ]
   }
   }
   """

Extracting the data:

do {
    let details2: PortfolioResponseModel = try JSONDecoder().decode(PortfolioResponseModel.self, from: myJSONArray.data(using: .utf8)!)
    //print(details2) 
    //print(details2.dataset.data[0]) //somehow get "2018-01-19"

} catch {
    print(error)
}

Solution

  • I cannot use [Any] here.

    Never use Any when decoding JSON because usually you do know the type of the contents.

    To decode an array you have to use an unkeyedContainer and decode the values in series

    struct PortfolioResponseModel: Decodable {
        var dataset: Dataset
    
        struct Dataset: Decodable {
            var data: [PortfolioData]
    
            struct PortfolioData: Decodable {
                let date : String
                let value : Double
    
                init(from decoder: Decoder) throws {
                    var container = try decoder.unkeyedContainer()
                    date = try container.decode(String.self)
                    value = try container.decode(Double.self)
                }
            }
        }
    }
    

    You can even decode the date strings as Date

    struct PortfolioData: Decodable {
        let date : Date
        let value : Double
    
        init(from decoder: Decoder) throws {
            var container = try decoder.unkeyedContainer()
            date = try container.decode(Date.self)
            value = try container.decode(Double.self)
        }
    }
    

    if you add a date formatter to the decoder

    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .formatted(formatter)
    let details2 = try decoder.decode(PortfolioResponseModel.self, from: Data(myJSONArray.utf8))