Decodable for JSON with two structs under the same tag

假如想象 提交于 2021-02-17 05:26:05

问题


I have this json:

{ "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 }}
]}

As you can see for "car" and "house" there are different "object" structs, but both under the tag "object".

It would be ideal if one ended up with something like

struct StuffItem: Decodable {       
  let type: TheType
  let car: Car
  let house: House
}

Is there some Codable, swifty, way to handle this?


回答1:


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



回答2:


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



回答3:


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


来源:https://stackoverflow.com/questions/58207841/decodable-for-json-with-two-structs-under-the-same-tag

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