0
votes

I have been struggling to develop a code to decode multiple different JSON strings into a combined data structure (struct).

As you can see in the code below, the two JSON strings correctInput and faultyInput, contain the same values but the key has a different name. Is there a way to decode such different strings (2 or more) into a common Codable struct?

Thank you!

import Foundation

struct Fruit: Codable, Equatable {
    var apple: String
    var banana: String
    var pineapple: String
}

let correctInput = """
{
    "apple": "Akane",
    "banana": "Bria",
    "pineapple": "Sarawak"
}
""".data(using: .utf8)!

let faultyInput = """
{
    "appl": "Akane",
    "nana": "Bria",
    "pine": "Sarawak"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let correctFruit = try? decoder.decode(Fruit.self, from: correctInput)
let faultyFruit = try? decoder.decode(Fruit.self, from: faultyInput)

print(correctFruit)
print(faultyFruit)
1
Yes, faultyInput always has the same keys. What do you mean with init(from:)? - GatCode
I did, but the documentation does only state how I can use e.g. the key appl to fill the the stuct, but not how I can fill the stuct with both appl and apple. Or more complicated: The apple var in the Fruit struct should be filled with either appl or apple or a - GatCode

1 Answers

0
votes

Here is a solution where we have a separate struct for the faulty data with a built in mapping function

struct FaultyFruit: Codable, Equatable {
    var appl: String
    var nana: String
    var pine: String

    func asFruit() -> Fruit {
        Fruit(apple: appl, banana: nana, pineapple: pine)
    }
}

And then we decode like this

var fruit: Fruit?
do {
    fruit = try? decoder.decode(Fruit.self, from: faultyInput)
    if fruit == nil {
        let faultyFruit = try decoder.decode(FaultyFruit.self, from: faultyInput)
        fruit = faultyFruit.asFruit()
    }
} catch {
    print(error)
}

And here is a solution where a custom init(from:) is used and different keys/property names are tried.

init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let values = try container.decode([String: String].self)
    apple = values["apple"] ?? values["appl"] ?? values["a"] ?? ""
    banana = values["banana"] ?? values["nana"] ?? ""
    pineapple = values["pineapple"] ?? values["pine"] ?? ""
}

Quietly assigning "" to a property might not be the correct way so you could add a check for nil and throw an error if no value could be parsed for a property.