Search code examples
jsonobjective-cswiftnsarraycgpoint

Convert JSON array of numbers into CGPoints in Objective-C or Swift


I am trying to convert JSON from an API into an array of cgpoints that I can use in core graphics.

The JSON looks like:

{"date":"2017-01-05","open":119.34,"high":120.2,"low":119.1,"close":119.7,"adjusted_close":109.7062,"volume":4261070},{"date":"2017-01-06","open":118.93,"high":121.5,"low":118.52,"close":120.76,"adjusted_close":110.6777,"volume":4089186},{"date":"2017-01-09","open":120.76,"high":121.06,"low":120.33,"close":120.43,"adjusted_close":110.3754,"volume":3021844}

I am interested in the date and close fields and I'd like to convert them into this format

 dataPoints:@[xy(val0,119.7),xy(val1,120.76),xy(val2,120.43)];

where val0...val3 are dates mapped to the x dimension of the graph and I have defined:

 #define xy(__x,__y) [NSValue valueWithCGPoint:CGPointMake(__x,__y)]

I am not wedded to the #define. It's just the code that CoreGraphics is using now. If there is another way to do it a struct or something that would be great.

I know how to decode the JSON into an array using:

struct StockReport: Codable {
    let date: String
    let stockDatumOpen, high, low, close: Double
    let adjustedClose: Double
    let volume: Int

    enum CodingKeys: String, CodingKey {
        case date
        case stockDatumOpen = "open"
        case high, low, close
        case adjustedClose = "adjusted_close"
        case volume
    }
}

typealias StockData = [StockReport]

let stockData = try? newJSONDecoder().decode(StockData.self, from: jsonData)

Beyond that, however, I'm not sure how to create into an array of CGpoints.

I imagine I can convert the date strings into seconds since 1970 (epoch) and then scale those to the coordinate system. Or I can use the index of the array as an integer corresponding to each day but that's as far as I've gotten. Should I loop through the array stockData and create points one by one? Is there a more elegant way?

Thanks in advance for any suggestions.


Solution

  • Assuming you are receiving a valid json string (the string you have posted it is not a valid json string. You can sort your objects by its date (if they are not already sorted), enumerate the elements and initialize a CGPoint as follow:


    let json = """
        [{"date":"2017-01-05",
         "open":119.34,
         "high":120.2,
         "low":119.1,
         "close":119.7,
         "adjusted_close":109.7062,
         "volume":4261070},
        {"date":"2017-01-06",
         "open":118.93,
         "high":121.5,
         "low":118.52,
         "close":120.76,
         "adjusted_close":110.6777,
         "volume":4089186},
        {"date":"2017-01-09",
         "open":120.76,
         "high":121.06,
         "low":120.33,
         "close":120.43,
         "adjusted_close":110.3754,
         "volume":3021844}]
    """
    

    do {
        let stocks = try JSONDecoder().decode([StockReport].self, from: Data(json.utf8))
        let points = stocks.sorted{$0.date < $1.date}.enumerated().map {
            CGPoint(x: Double($0.offset), y: $0.element.close)
        }
        print(points)
    } catch {
        print(error)
    }
    

    This will print

    [(0.0, 119.7), (1.0, 120.76), (2.0, 120.43)]