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

前端 未结 4 911
暖寄归人
暖寄归人 2020-12-02 13:21

I\'m using the Swift 4 Codable protocol with JSON data. My data is formatted such that there is a single key at the root level with an object value containing t

相关标签:
4条回答
  • 2020-12-02 13:42

    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.

    0 讨论(0)
  • 2020-12-02 13:50

    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.

    0 讨论(0)
  • 2020-12-02 14:04

    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)
    
    0 讨论(0)
  • 2020-12-02 14:04

    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 ;)

    0 讨论(0)
提交回复
热议问题