问题
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