Swift 4 Codable; How to decode object with single root-level key

梦想的初衷 提交于 2019-11-27 19:01:54

You could decode using a dictionary: user combination then extract out the user object. e.g.

struct User: Codable {
    let id: Int
    let username: String
}

let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)

Ollie's answer is definitely the best way to go for this case, but it does push some knowledge into the caller, which may be undesirable. It also isn't very flexible. I still think it's a great answer and exactly what you want here, but this is a nice simple example to explore custom structural encoding.

How can we make this work correctly:

let user = try? JSONDecoder().decode(User.self, from: json)

We can't use the default conformances anymore. We have to build our own decoder. That's slightly tedious, but not difficult. First, we need to encode the structure into CodingKeys:

struct User {
    let id: Int
    let username: String

    enum CodingKeys: String, CodingKey {
        case user // The top level "user" key
    }

    // The keys inside of the "user" object
    enum UserKeys: String, CodingKey {
        case id
        case username
    }
}

With that, we can decode User by hand by pulling out the nested container:

extension User: Decodable {
    init(from decoder: Decoder) throws {

        // Extract the top-level values ("user")
        let values = try decoder.container(keyedBy: CodingKeys.self)

        // Extract the user object as a nested container
        let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)

        // Extract each property from the nested container
        id = try user.decode(Int.self, forKey: .id)
        username = try user.decode(String.self, forKey: .username)
    }
}

But I'd absolutely do it Ollie's way for this problem.

For much more on this see Encoding and Decoding Custom Types.

Of course, you can always implement your own custom decoding/encoding — but for this simple scenario your wrapper type is a much better solution IMO ;)

For comparison, the custom decoding would look like this:

struct User {
    var id: Int
    var username: String

    enum CodingKeys: String, CodingKey {
        case user
    }

    enum UserKeys: String, CodingKey {
        case id, username
    }
}

extension User: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
        self.id = try user.decode(Int.self, forKey: .id)
        self.username = try user.decode(String.self, forKey: .username)
    }
}

and you still to conform to the Encodable protocol if you want to support encoding as well. As I said before, your simple UserWrapper is much easier ;)

I created a helper extension for Codable that will make things like this easier.

see https://github.com/evermeer/Stuff#codable

With that you can create an instance of your user object like this:

    let v = User(json: json, keyPath: "user")

You don't have to change anything in your original User struct and you don't need a wrapper.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!