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?
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)
}
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With