How to encode enum with Arrays of Custom Objects

890 Views Asked by At

Thanks for your help in advance! I am relatively new to SwiftUI and have been struggling with encoding an enum with Arrays of custom objects. Here is the code:

struct ChartData: Codable {
    var data: DataType
...
    private enum CodingKeys : String, CodingKey { case id, title, type, info, label_x, label_y, last_update, data }

    func encode(to encoder: Encoder) throws {
       var container = encoder.container(keyedBy: CodingKeys.self)
...
        try container.encode(self.data, forKey: .data)
}
}

enum DataType: Codable{
    case plot([ChartPlotData])
    case slice([ChartSliceData])
    case number([ChartNumberData])
}

struct ChartPlotData: Codable {
    var chart_id: String
    var order_plot: Int
    var label_plot: String?
    var points_x: [String]
    var points_y: [Double]
    
    private enum CodingKeys : String, CodingKey { case chart_id, order_plot, label_plot, points_x, points_y }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.chart_id = try container.decode(String.self, forKey: .chart_id)
        self.order_plot = try container.decode(Int.self, forKey: .order_plot)
        self.label_plot = try? container.decode(String.self, forKey: .label_plot)
        do{
            self.points_x = try container.decode([String].self, forKey:.points_x)
        }catch{
            let xs = try container.decode([Double].self, forKey:.points_x)
            self.points_x = xs.map { String($0) }
        }
        self.points_y = try container.decode([Double].self, forKey:.points_y)
    }
}

struct ChartSliceData: Codable {
    var chart_id: String
    var order_slice: Int
    var label_slice: String
    var value_slice: Double
}

struct ChartNumberData: Codable {
    var chart_id: String
    var number: Double
    var unit: String?
}

I am attempting to cache JSON in UserDefaults so as to avoid having to make extraneous API calls. Using JSONEncoder, I am left with the following JSON snippet (excerpted from a much longer string):

            "data" : {
              "plot" : {
                "_0" : [
                  {
                    "label_plot" : "China",
                    "points_y" : [
                      0,
                      0,
                      0,
...

However, I am looking to get an encoding like this:

            "data" : [
                  {
                    "label_plot" : "China",
                    "points_y" : [
                      0,
                      0,
                      0,
...

Any help would be greatly appreciated! Thanks so much!

1

There are 1 best solutions below

2
Joakim Danielson On BEST ANSWER

This can be solved by adding an extra item in the CodingKeys enum for encoding and decoding the type used for the DataType enum and then using a switch to encode and decode the right type of array.

Here is the full ChartData struct although with some properties and code removed for brevity

struct ChartData: Codable {
    var id: Int
    var title: String
    var data: DataType
   
    enum CodingKeys: String, CodingKey {
        case id, title, data, type
    }
   
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(title, forKey: .title)
        
        switch data {
        case .number(let values): 
            try container.encode("number", forKey: .type)
            try container.encode(values, forKey: .data)
        case .plot(let values): 
            try container.encode("plot", forKey: .type)
            try container.encode(values, forKey: .data)
        case .slice(let values): 
            try container.encode("slice", forKey: .type)
            try container.encode(values, forKey: .data)
        }
    }
    
    init(from decoder: Decoder) throws {
        var container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        title = try container.decode(String.self, forKey: .title)
        
        let type = try container.decode(String.self, forKey: .type)
        switch type {
        case "number":
            let values = try container.decode([ChartNumberData].self, forKey: .data)
            data = .number(values)
                // case ...
        default:
            fatalError("Unsupported type for DataType: \(type)")
        }
    }
}