Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expected to decode Dictionary<String, Any> but found an array instead with Nested Containers

So, I am trying to parse this JSON using the Codable protocols: https://randomuser.me/api/?results=100

That are basically 100 random users.

Here's my User class initializer from decoder, that I need because the User is an entity in a Core Data Model:

required convenience public init(from decoder: Decoder) throws {
        let managedObjectContext = CoreDataStack.sharedInstance.persistentContainer.viewContext
        guard let entity = NSEntityDescription.entity(forEntityName: "User", in: managedObjectContext) else {
                fatalError("Failed to decode User")
        }

        self.init(entity: entity, insertInto: managedObjectContext)

        let container = try decoder.container(keyedBy: CodingKeys.self)
        let results = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .results)
        let name = try results.nestedContainer(keyedBy: CodingKeys.self, forKey: .name)
        firstName = try name.decode(String.self, forKey: .firstName)
        lastName = try name.decode(String.self, forKey: .lastName)

        let location = try results.nestedContainer(keyedBy: CodingKeys.self, forKey: .location)
        let street = try location.decode(String.self, forKey: .street)
        let city = try location.decode(String.self, forKey: .city)
        let postcode = try location.decode(String.self, forKey: .postcode)
        address = street + ", " + city + ", " + postcode

        email = try results.decode(String.self, forKey: .email)

        let pictures = try results.nestedContainer(keyedBy: CodingKeys.self, forKey: .pictures)
        pictureURL = try pictures.decode(String.self, forKey: .pictureURL)
    }

This is the defective line:

let results = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .results)

Here's the complete error:

typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))

I think it's due to the structure of the JSON, that is an array of 100 elements under the key "results", and I think the problem could be in doing them all together. How should I handle this?

like image 874
user7434770 Avatar asked Aug 26 '26 08:08

user7434770


2 Answers

The error is clear: The value for results is an array and nestedContainers expects a dictionary.

To decode the user array you need an umbrella struct outside of the Core Data classes.

struct Root : Decodable {
   let results: [User]
}

While decoding Root the init method in User is called for each array item.

To use nestedContainers you have to separate the CodingKeys.

This is the init method without the Core Data stuff. postcode can be String or Int

private enum CodingKeys: String, CodingKey { case name, location, email, picture }
private enum NameCodingKeys: String, CodingKey { case first, last }
private enum LocationCodingKeys: String, CodingKey { case street, city, postcode }
private enum PictureCodingKeys: String, CodingKey { case large, medium, thumbnail }

required convenience public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let nameContainer = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
    let firstName = try nameContainer.decode(String.self, forKey: .first)
    let lastName = try nameContainer.decode(String.self, forKey: .last)

    let locationContainer = try container.nestedContainer(keyedBy: LocationCodingKeys.self, forKey: .location)
    let street = try locationContainer.decode(String.self, forKey: .street)
    let city = try locationContainer.decode(String.self, forKey: .city)
    let postcode : String
    do {
        let postcodeInt = try locationContainer.decode(Int.self, forKey: .postcode)
        postcode = String(postcodeInt)
    } catch DecodingError.typeMismatch {
        postcode = try locationContainer.decode(String.self, forKey: .postcode)
    }
    let address = street + ", " + city + ", " + postcode

    let email = try container.decode(String.self, forKey: .email)

    let pictureContainer = try container.nestedContainer(keyedBy: PictureCodingKeys.self, forKey: .picture)
    let pictureURL = try pictureContainer.decode(URL.self, forKey: .large)
}
like image 114
vadian Avatar answered Aug 29 '26 04:08

vadian


This is a very simplified version but it handles your Json data correctly

struct Result : Codable {
    let results: [User]
}
struct User: Codable {
    let gender: String
    let name: Name
}
struct Name: Codable {
    let title: String
    let first: String
    let last: String
}

let decoder = JSONDecoder()
let data = jsonString.data(using: .utf8) //Replace with url download

do {
    let json = try decoder.decode(Result.self, from: data!)
} catch {
    print(error)
}
like image 28
Joakim Danielson Avatar answered Aug 29 '26 03:08

Joakim Danielson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!