Decodable for JSON with two structs under the same tag

前端 未结 3 1460
灰色年华
灰色年华 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:06

    You can handle multiple cases by using the enum just define your type, Giving code will help you to parse the JSON by using Struct modal with enum.

    // MARK: - Welcome
    struct Welcome: Codable {
        let stuff: [Stuff]
    }
    
    // MARK: - Stuff
    struct Stuff: Codable {
        let type: String
        let object: Object
    }
    
    // MARK: - Object
    struct Object: Codable {
        let a, b, c, d: Int?
        let e, f: Int?
    }
    
    enum Type: String {
        case car
        case house
    }
    
    func fetchResponse() {
        do {
            let jsonString = "your json string"
            let data = Data(jsonString.utf8)
            let result = try JSONDecoder().decode(Welcome.self, from: data)
            let objects = result.stuff
            let carObjects = objects.filter{$0.type == Type.car.rawValue}
            print("Its car array: \(carObjects)")// if you need filters car object then use this
            let houseObjects = objects.filter{$0.type == Type.house.rawValue}// if you need filters house object then use this
            print("Its house array: \(houseObjects)")
            // or you check in loop also
            objects.forEach { (stuff) in
                switch stuff.type {
                case Type.car.rawValue:
                    print("Its car object")
                case Type.house.rawValue:
                    print("Its house object")
                default:
                    print("Also you can set your one case in `default`")
                    break
                }
            }
        } catch {
            print(error.localizedDescription)
        }
    }
    
    0 讨论(0)
  • 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)
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-27 09:27

    The swiftiest way in my opinion is an enum with associated types

    This is valid JSON

    let jsonString = """
    { "stuff": [
        {
        "type":"car",
        "object":{
            "a":66,
            "b":66,
            "c":66
            }
        },{
        "type":"house",
        "object":{
            "d":66,
            "e":66,
            "f":66
            }
        },{
        "type":"car",
        "object":{
            "a":66,
            "b":66,
            "c":66
            }
        }
    ]}
    """
    

    These are the structs

    struct Root : Decodable {
        let stuff : [Object]
    }
    
    enum Type : String, Decodable { case car, house }
    
    struct Car : Decodable {
        let a, b, c : Int
    }
    
    struct House : Decodable {
        let d, e, f : Int
    }
    
    
    enum Object : Decodable {
        case house(House), car(Car)
    
        private enum CodingKeys : String, CodingKey { case type, object }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let type = try container.decode(Type.self, forKey: .type)
            switch type {
            case .car:
                let carData = try container.decode(Car.self, forKey: .object)
                self = .car(carData)
            case .house:
                let houseData = try container.decode(House.self, forKey: .object)
                self = .house(houseData)
            }
        }
    }
    

    And the code to decode the JSON

    do {
        let result = try JSONDecoder().decode(Root.self, from: Data(jsonString.utf8))
        let objects = result.stuff
        for object in objects {
            switch object {
            case .car(let car): print(car)
            case .house(let house): print(house)
            }
        }
    } catch {
        print(error)
    }
    
    0 讨论(0)
提交回复
热议问题