Search code examples
swiftswiftuidecodable

SwiftUI - How can I access API response data with a dynamic key?


Thanks for the help in advance.
I'm working on an API call in SwiftUI for the first time. I've got a portion of the data back by using structs/decodable. And I use enums to access each layer. However, the API that I'm using uses the current date as a key, and I'm struggling to access that layer of data without using a string literal. Any Ideas?
Here's what I have.

struct NEObjects: Decodable {
    let fromToday: [NEObject]
    
    enum CodingKeys: String, CodingKey {
        case  fromToday = "2021-02-16"
    }
}

I have the current date stored in the user defaults. But if I do something like

let date = UserDefaults.standard.string(forKey: "current_date")
case fromToday = date

Then I get an error saying "Raw value for enum case must be a literal"
Im' not sure how else I can access this layer because I'm new to swiftUI.
Here is the full data model for reference:

import Foundation

let date = UserDefaults.standard.string(forKey: "current_date")

struct NEOData: Decodable  {
    let elementCount: Int
    let neObjects: NEObjects
    
    enum CodingKeys: String, CodingKey {
        case elementCount = "element_count"
        case neObjects = "near_earth_objects"
    }
}

struct NEObjects: Decodable {
    let fromToday: [NEObject]
    
    enum CodingKeys: String, CodingKey {
        case  fromToday = "2021-02-15"
    }
}

struct NEObject: Decodable {
    let name: String
    let absoluteMagnitude: Double
    let diameter: Diameter
    let isHazardous: Bool
    let closeApproachData: [CloseApproachData]
    let isSentry: Bool
    
    enum CodingKeys: String, CodingKey {
        case name = "name"
        case absoluteMagnitude = "absolute_magnitude_h"
        case diameter = "estimated_diameter"
        case isHazardous = "is_potentially_hazardous_asteroid"
        case closeApproachData = "close_approach_data"
        case isSentry = "is_sentry_object"
    }
}

struct Diameter: Decodable {
    let kilometers: Kilometers
    let meters: Meters
    let miles: Miles
    let feet: Feet
}

struct CloseApproachData: Decodable {
    let velocity: Velocity
    let missDistance: MissDistance
    let date: String
    
    enum CodingKeys: String, CodingKey {
        case velocity = "relative_velocity"
        case missDistance = "miss_distance"
        case date = "close_approach_date"
    }
}

struct Velocity: Decodable {
    let mph: String
    let kph: String
    
    enum CodingKeys: String, CodingKey {
        case mph = "miles_per_hour"
        case kph = "kilometers_per_hour"
    }
}

struct MissDistance: Decodable {
    let kilometers: String
    let miles: String
}

struct Kilometers: Decodable {
    let min: Double
    let max: Double
    
    enum CodingKeys: String, CodingKey {
        case min = "estimated_diameter_min"
        case max = "estimated_diameter_max"
    }
}

struct Meters: Decodable {
    let min: Double
    let max: Double
    
    enum CodingKeys: String, CodingKey {
        case min = "estimated_diameter_min"
        case max = "estimated_diameter_max"
    }
}

struct Miles: Decodable {
    let min: Double
    let max: Double
    
    enum CodingKeys: String, CodingKey {
        case min = "estimated_diameter_min"
        case max = "estimated_diameter_max"
    }
}

struct Feet: Decodable {
    let min: Double
    let max: Double
    
    enum CodingKeys: String, CodingKey {
        case min = "estimated_diameter_min"
        case max = "estimated_diameter_max"
    }
}

Solution

  • Without seeing the JSON, I'm just guessing you want your NEObjects struct to look like this:

    struct NEObjects: Decodable {
        let objectsByDay : [String : NEObject]    
    }
    

    This would decode JSON that looks like this:

    {
       "2021-02-16" : [
           { ... a bunch of your custom objects ... }
       ]
    }