Home > Net >  Decoding JSON in Swift and Ignoring Not Available JSON Properties/Keys
Decoding JSON in Swift and Ignoring Not Available JSON Properties/Keys

Time:03-25

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)
  • Related