Home > Blockchain >  Swift Codable: Include dictionary key as property in decoded Codable object
Swift Codable: Include dictionary key as property in decoded Codable object

Time:01-13

I have a JSON object as such:

{
    "red":
        {
            "a": 1,
            "b": 2,
            "c": 3
        }
    "yellow":
        {
            "a": 1,
            "b": 2,
            "c": 3
        }
    "blue":
        {
            "a": 1,
            "b": 2,
            "c": 3
        }
}

I decode each of these into a Color object marked as Codable.

I would like to include the key of the object as a property of the object itself, such that I can differentiate between the keys to provide supplementary information, such as having a function that can provide a color to pair with the object (e.g. for 'red', pair it with 'blue').

How can I include the dictionary key as a property on the Codable object itself?

CodePudding user response:

A possible way is to add a temporary struct for the color object, then decode the dictionary as [String:Temp] and map the data to the real struct. As Color is part of SwiftUI I named the struct MyColor

struct Temp: Decodable { let a, b, c : Int }

struct Root : Decodable {
    let colors : [MyColor]

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let colorData = try container.decode([String:Temp].self)
        colors = colorData.map{ MyColor(name: $0.key, a: $0.value.a, b: $0.value.b, c: $0.value.c) }
    }
}

struct MyColor : Decodable {
    let name: String
    let a, b, c : Int
}

Then decode

JSONDecoder().decode(Root.self...

However if the keys are static drop the Temp struct and use this

struct Root : Decodable {
    let red : MyColor
    let yellow : MyColor
    let blue : MyColor
}

struct MyColor : Decodable {
    let a, b, c : Int
}

CodePudding user response:

Based on @vadian answer, you could try this approach, using init(from decoder: Decoder) to decode the json data, and a Colour struct with a name and id that you can use.

struct ColorResponse: Decodable {
    var red: Colour
    var yellow: Colour
    var blue: Colour
    
    enum CodingKeys: String, CodingKey {
        case red,yellow,blue
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        red = try container.decode(Colour.self, forKey: .red)
        red.name = "red"
        yellow = try container.decode(Colour.self, forKey: .yellow)
        yellow.name = "yellow"
        blue = try container.decode(Colour.self, forKey: .blue)
        blue.name = "blue"
    }
}

struct Colour: Identifiable, Decodable {
    let id = UUID()
    var name: String = ""
    var a, b, c : Int
    
    enum CodingKeys: String, CodingKey {
        case a,b,c
    }
}

struct ColorView: View {
    @State var colour: Colour
    
    var body: some View {
       VStack {
           Text(colour.name)
           Text("\(colour.a)")
           Text("\(colour.b)")
           Text("\(colour.c)")
       }
    }
}

struct ContentView: View {
    @State var colours: [Colour] = []
    
    var body: some View {
        List(colours) { col in
            ColorView(colour: col)
        }
        .onAppear {
            let json = """
            {
                "red":  {  "a": 1, "b": 2,  "c": 3  },
                "yellow": {  "a": 1, "b": 2,  "c": 3  },
                "blue":  {  "a": 1, "b": 2, "c": 3  }
            }
            """
            // simulated API data
            let data = json.data(using: .utf8)!
            do {
                let results = try JSONDecoder().decode(ColorResponse.self, from: data)
                colours.append(results.blue)
                colours.append(results.red)
                colours.append(results.yellow)
                colours.forEach{print("---> colours: \($0)")}
            } catch {
                print("\n---> decoding error: \n \(error)\n")
            }
        }
    }
}
  • Related