I am having issues with decoding JSON, where some of the fields returned from the server as not available on the client. Take a look at the following code. The JSON consists of three roles but the Role enum only consists of student and staff. How can I successfully decode JSON ignoring the missing faculty role.
let json = """
[
{
"role": "student"
},
{
"role": "staff"
},
{
"role": "faculty"
},
]
"""
struct User: Decodable {
let role: Role
}
enum Role: String, Decodable {
case student
case staff
private enum CodingKeys: String, CodingKey {
case student = "student"
case staff = "staff"
}
}
let decoded = try! JSONDecoder().decode([User].self, from: json.data(using: .utf8)!)
print(decoded)
Currently, I get the following error:
error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 2", intValue: 2), CodingKeys(stringValue: "role", intValue: nil)], debugDescription: "Cannot initialize Role from invalid String value faculty", underlyingError: nil))
CodePudding user response:
It will always throw that error because the decoder will be presented with "role": "faculty" which it won't recognize. Thus if you really really want to ignore it you can ditch the decoder and do something like this:
guard let json = try? JSONSerialization.jsonObject(with: json.data(using: .utf8)!, options: .mutableContainers) as? [[String: Any]] else {return}
let users: [User] = json.filter({$0["role"] != "faculty")}.map({User(role: Role(rawValue: $0["role"])!)})
Or you can add the missing case and filter the array:
users = users.filter({$0 != Role.faculty})
CodePudding user response:
You'll need to create a top-level object to hold the [User]
:
struct UserList {
var users: [User]
}
Then, give it a custom decoder that will check for malformed role
keys and ignore those Users, but won't ignore other errors:
extension UserList: Decodable {
// An empty object that can decode any keyed container by ignoring all keys
private struct Ignore: Decodable {}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var users: [User] = []
while !container.isAtEnd {
do {
// Try to decode a User
users.append(try container.decode(User.self))
}
// Check for the specific failure of "role" not decoding.
// Other errors will still be thrown normally
catch DecodingError.dataCorrupted(let context)
where context.codingPath.last?.stringValue == "role" {
// You still need to decode the object into something, but you can ignore it.
_ = try container.decode(Ignore.self)
}
}
self.users = users
}
}
let decoded = try JSONDecoder().decode(UserList.self,
from: json.data(using: .utf8)!).users
// [User(role: Role.student), User(role: Role.staff)]
If you define User.CodingKeys
, then you can skip the Ignore struct:
struct User: Decodable {
// The auto-generated CodingKeys is private, so you have to
// define it by hand to access it elsewhere
enum CodingKeys: String, CodingKey {
case role
}
let role: Role
}
With that, you can get rid of the Ignore struct and replace this line:
_ = try container.decode(Ignore.self)
with:
_ = try container.nestedContainer(keyedBy: User.CodingKeys.self)