Search code examples
arraysjsonswiftjsondecoder

Decode a JSON file that contains string keys each of which contains one key value pair into [(Date,Double)]


I am trying to determine how to read a JSON file that consist of a series of strings and underneath each is one key value pair into [(Date,Double)]. I have been able to do so by manually adding a main key "Time Series (Daily)" to the top of the JSON file and the structs below return [(Date,Double)]. I would like to be able to eliminate the step of adding "Time Series (Daily)" to the JSON file but still return [(Date,Double)]. Any insight on how to achieve these results would be appreciated.

{
    "Time Series (Daily)": { // this entire line is manually added to JSON file
        "20200803": {
            "NAV": 173.94769
        },
        "20200804": {
            "NAV": 174.57441
        },

struct PrincipalTimeSeriesData {

    var timeSeriesDaily: [(Date, Double)]
}

extension PrincipalTimeSeriesData: Decodable {
    
    enum CodingKeys: String, CodingKey {
        case timeSeriesDaily = "Time Series (Daily)"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        timeSeriesDaily = try container
                .decode([String:PrincipalTimeSeriesDaily].self, forKey: .timeSeriesDaily)
                .map { (dateFormatterPrin.date(from: $0)!, $1.close) }
    }
}

struct PrincipalTimeSeriesDaily {

    let close: Double
    
}

extension PrincipalTimeSeriesDaily: Decodable {

    enum CodingKeys: String, CodingKey {
        case close = "NAV"
    }
}

Solution

  • You can decode the data as [String:PrincipalTimeSeriesDaily] and then map the keys/values of the resulting Dictionary to your desired format:

    let jsonData = """
    {
            "20200803": {
                "NAV": 173.94769
            },
            "20200804": {
                "NAV": 174.57441
            },
    }
    """.data(using: .utf8)!
    
    let dateFormatterPrin = DateFormatter()
    dateFormatterPrin.dateFormat = "yyyyMMdd"
    
    
    struct PrincipalTimeSeriesDaily {
    
        let close: Double
        
    }
    
    extension PrincipalTimeSeriesDaily: Decodable {
    
        enum CodingKeys: String, CodingKey {
            case close = "NAV"
        }
    }
    
    do {
        let decoded = try JSONDecoder().decode([String:PrincipalTimeSeriesDaily].self, from: jsonData)
        let converedToDateKeysArray = decoded.map { item -> (Date,Double) in
            (dateFormatterPrin.date(from: item.key)!,item.value.close)
        }.sorted { $0.0 < $1.0 }
        print(converedToDateKeysArray)
    } catch {
        print(error)
    }