1

Hello people I have a little question related with the Encodable protocol in Swift.

I have the following json file:

let magicJson = """
{
    "value": [
        {
        "scheduleId": "[email protected]",
        "somethingEventMoreMagical": "000220000"
        }
    ]
}
""".data(using: .utf8)!

For decoding I tried to avoid having to create two objects that both go with Decodable, and the first one has an array of the second object. I would like to flatten that object into something like this:

struct MagicalStruct: Decodable {
    private enum CodingKeys: String, CodingKey {
        case value
    }
    
    private enum ScheduleCodingKeys: String, CodingKey {
        case roomEmail = "scheduleId"
    }
    
    let roomEmail: String
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let magicContainer = try container.nestedContainer(keyedBy: ScheduleCodingKeys.self, forKey: .value)
        roomEmail = try magicContainer.decode(String.self, forKey: ScheduleCodingKeys.roomEmail)
    }
}

However when I try the following code: JSONDecoder().decode(MagicalStruct.self, magicJson) I get that it expects an array but gets a dictionary. On the other hand when I go with JSONDecoder().decode([MagicalStruct].self, magicJson), I get that it receives an array but expects a dictionary.

Does anyone know why this is happening ?

2
  • 2
    You don't seem to have scheduleId key in your input JSON. Commented Jul 21, 2020 at 18:46
  • I edited wrong the json, it was scheduleId in it as well, let me edit :D Commented Jul 22, 2020 at 5:47

1 Answer 1

1

First when you're decoding your struct using:

JSONDecoder().decode(MagicalStruct.self, magicJson)

you're trying to extract a single object: let roomEmail: String.

However, your input JSON contains an array of objects with emails. Which means your code:

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let magicContainer = try container.nestedContainer(keyedBy: ScheduleCodingKeys.self, forKey: .value)
    roomEmail = try magicContainer.decode(String.self, forKey: ScheduleCodingKeys.roomEmail)
}

tries to decode a single email and there is a collection instead (in your example with one element - that's why it may be confusing).

Also your error Expected to decode Dictionary<String, Any> but found an array instead is on the line:

let magicContainer = try container.nestedContainer(keyedBy: ScheduleCodingKeys.self, forKey: .value)

You need to decode an array:

var magicContainer = try container.nestedUnkeyedContainer(forKey: .value)

But then you have an array of objects with scheduleId and somethingEventMoreMagical keys. How do you want to assign all the values to your one let roomEmail: String variable?


You can decode a dictionary instead:

let result = try JSONDecoder().decode([String: [MagicalStruct]].self, from: magicJson)

print(result["value"]!) // prints [MagicalStruct(roomEmail: "[email protected]")]

And you can simplify your MagicalStruct:

struct MagicalStruct: Decodable {
    enum CodingKeys: String, CodingKey {
        case roomEmail = "scheduleId"
    }

    let roomEmail: String
}
Sign up to request clarification or add additional context in comments.

2 Comments

Hey:) Thank you for your answer, this is indeed a nice solution, but what I was looking for, is not a direct direction/solution, but rather why Swift doesn't allow what I describe above with Decodable. If you have any idea, feel free to answer I would be very happy to hear more opinions/solutions :)
@VladSima I updated my answer with a better explanation. Basically all your problems occur because you try to parse an array to a single object. You need to choose what do you want: return an array of [MagicalStruct], make let roomEmail: String an array instead of String or just parse the first email from the array.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.