Since the "resource" key determine what the data in the "rowSet" key mean, I would model this as a enum with associated values.
First, create models for the two kinds of resources. Add decoding initialisers that allows them to be decoded from JSON arrays.
// I only implemented the Decodable side.
// The Encodable side should be trivial to do once you understand the idea
struct ResourceA: Decodable {
// not sure what these properties mean...
let property1: String
let property2: Int
let property3: String
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
property1 = try container.decode(String.self)
property2 = try container.decode(Int.self)
property3 = try container.decode(String.self)
}
}
struct ResourceB: Decodable {
let dateString: String
let score1: Int
let score2: Int
let result: String
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
dateString = try container.decode(String.self) // I'm a bit lazy - you can parse this to a Date on your own :)
score1 = try container.decode(Int.self)
score2 = try container.decode(Int.self)
result = try container.decode(String.self)
}
}
Then change ResultSet
to an enum
with cases corresponding to the types of resources. In the decoding initialiser, you first decode the "resource" key, and switch
on that to decide which kind of resource to decode for the "rowSet" key.
enum ResultSet: Decodable {
// if the headers can be computed from the resource type,
// you don't need it as an associated value - just add it as a computed property instead
case resourceA([ResourceA], headers: [String])
case resourceB([ResourceB], headers: [String])
enum CodingKeys: CodingKey {
case resource
case headers
case rowSet
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let resourceName = try container.decode(String.self, forKey: .resource)
let headers = try container.decode([String].self, forKey: .headers)
switch resourceName {
case "resource A":
self = .resourceA(try container.decode([ResourceA].self, forKey: .rowSet), headers: headers)
case "resource B":
self = .resourceB(try container.decode([ResourceB].self, forKey: .rowSet), headers: headers)
default:
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Unknown resource name \(resourceName)"))
}
}
}
Example usage:
let json = """
[{
"resource": "resource A",
"headers": [],
"rowSet": [
["A", 1, "test1"],
["B", 2, "test2"]
],
},
{
"resource": "resource B",
"headers": [],
"rowSet": [
["2/28/2022", 1, 4, "loss"],
["3/28/2022", 2, 3, "win"]
],
}]
""".data(using: .utf8)!
let decoded = try JSONDecoder().decode([ResultSet].self, from: json)