Decodable for JSON with two structs under the same tag

前端 未结 3 1462
灰色年华
灰色年华 2021-01-27 08:44

I have this json:

{ \"stuff\": [
 {
 \"type\":\"car\",
 \"object\":{
  \"a\":66,
  \"b\":66,
  \"c\":66 }},
 {
 \"type\":\"house\",
 \"object\":{
  \"d\":66,
  \         


        
3条回答
  •  清歌不尽
    2021-01-27 09:24

    Another explantion:

    Since there are not many explanations of this around, here's another example of what @vadian has explained:

    1. In Swift, use enum instead of struct to achieve 'flexible type'.

    2. You then need parsers for the 'item', and for the 'type'.

    2. Parse, and then just switch to decode, and set the correct type.

    So for the JSON above, you'd have

    struct YourFeed: Decodable {
        let stuff: [Item]
    }
    

    Each item can be a Car or a House.

    struct Car: Decodable { ... }
    struct House: Decodable { ... }
    

    So those are easy.

    Now for Item. It can be more than one type.

    In Swift, use enum instead of struct to achieve 'flexible type'.

    // in Swift, an "enum" is basically a "struct" which can have a flexible type,
    // so here we have enum Item rather than struct Item:
    enum Item: Decodable {
    
        // so this thing, Item, can be one of these two types:
        case car(Car)
        case house(House)
    

    Next, simply mirror that in a raw enum which will be used for parsing the "type" field. (You can call it anything, I've just called it "Parse".)

        // the relevant key strings for parsing the "type" field:
        private enum Parse: String, Decodable {
            case car
            case house
        }
    

    Next, look at the original JSON up top. Each "item" has two fields, "type" and "object". Here they are in a raw enum. (Again you can call it anything, I've just called it "Keys" here.)

        // we're decoding an item, what are the top-level tags in item?
        private enum Keys: String, CodingKey {
            // so, these are just the two fields in item from the json
            case type
            case object
        }
    

    Have an enum to parse the 'item' level, and an enum to parse the 'type'.

    Finally, write the initializer for "Item". Simply decode the both the top level and the "type" ...

        init(from decoder: Decoder) throws {
    
            // parse the top level
            let c = try decoder.container(keyedBy: Keys.self)
    
            // and parse the 'type' field
            let t = try c.decode(Parse.self, forKey: .type)
    

    ... and you're done. Decode the data (using the relevant class), and set the "Item" enum object to the appropriate type.

    Parse those, and then just switch to decode / set the enum.

            // we're done, so depending on which of the types it is,
            // decode (using the relevant decoder), and become the relevant type:
            switch t {
            case .car:
                let d = try c.decode(Car.self, forKey: .object)
                self = .car(d)
            case .house:
                let d = try c.decode(House.self, forKey: .object)
                self = .house(d)
            }
        }
    }
    

    Here's the whole thing in one go:

    enum Item: Decodable {
        case car(Car)
        case house(House)
    
        // the relevant key strings for parsing the 'type' field:
        private enum Parse: String, Decodable {
            case car
            case house
        }
    
        // the top-level tags in 'item':
        private enum Keys: String, CodingKey {
            case type
            case object
        }
    
        init(from decoder: Decoder) throws {
    
            // parse the top level
            let c = try decoder.container(keyedBy: Keys.self)
    
            // parse the 'type' field
            let t = try c.decode(Parse.self, forKey: .type)
    
            // we're done, switch to
            // decode (using the relevant decoder), and become the relevant type:
            switch t {
            case .car:
                let d = try c.decode(Car.self, forKey: .object)
                self = .car(d)
            case .house:
                let d = try c.decode(House.self, forKey: .object)
                self = .house(d)
            }
        }
    }
    

提交回复
热议问题