Search code examples
jsonswiftjsondecoder

Decode JSON with variables in Swift


I am trying to decode this type of JSON-Data in Swift

{"Total ingredients":[{"PE-LLD":"54.4 %"},{"PE-HD":"41.1 %"},{"TiO2":"4.5 %"}]}

The name and number of ingredients is variable. Therefore I am only able to decode it in this type of structure:

struct Product: Codable {
    var total_ingredients: [[String: String]]?

    private enum CodingKeys : String, CodingKey {   
        case total_ingredients = "Total ingredients"
    }
}

But I would like to be able to decode it in either one dictionary: var total_ingredients: [String: String]? or my preferred choice in an array of objects: var total_ingredients: [Ingredient]?

struct Ingredient: Codable {
    var name: String
    var percentage: String
}

I already tried to solve my problem with an extension but it isn't working and I don't think that's the correct approach:

extension Ingredient {
    init(_ ingredient: [String: String]) {
        var key: String = ""
        var value: String = ""
        
        for data in ingredient {
            key = data.key
            value = data.value
        }
        
        self = .init(name: key, percentage: value)
    }
}

Thanks in advance :)


Solution

  • You have to implement init(from decoder and map the array of dictionaries to Ingredient instances

    struct Product: Decodable {
        let totalIngredients: [Ingredient]
    
        private enum CodingKeys : String, CodingKey { case totalIngredients = "Total ingredients" }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let ingredientData = try container.decode([[String:String]].self, forKey: .totalIngredients)
            totalIngredients = ingredientData.compactMap({ dict -> Ingredient? in
               guard let key = dict.keys.first, let value = dict[key] else { return nil }
               return Ingredient(name: key, percentage: value)
            })
        }
    }
    
    struct Ingredient {
        let name, percentage: String
    }
    
    let jsonString = """
    {"Total ingredients":[{"PE-LLD":"54.4 %"},{"PE-HD":"41.1 %"},{"TiO2":"4.5 %"}]}
    """
    
    let data = Data(jsonString.utf8)
    
    do {
        let result = try JSONDecoder().decode(Product.self, from: data)
        print(result)
    } catch {
        print(error)
    }
    

    The extension is not needed.