I am trying to decode some JSON from a server that looks like this:
{
"data": {
"fields": ["column1", "column2"],
"values": [
["value 1", { "property": 1 }],
["value 2", { "property": 2 }],
]
}
}
Notably, the inner arrays of the values
2D array can contain many different types of objects depending on the request I sent to the server. For context, I am using the HTTP Query API of a Neo4J database. The values
2D array contains the rows returned by the query. The inner arrays contain heterogeneous elements because the columns returned by the query can have different types.
For each request, I always know how many elements the inner arrays are going to contain and the type of each element. Therefore, I thought I should create a Decodable
type that has a parameter pack, representing the types of the elements in the array.
public struct NJQueryResponse<each Result: Decodable & Sendable>: Decodable, Sendable {
public let data: Data
public struct Data: Decodable, Sendable {
public let fields: [String]
public var rows: [(repeat each Result)]
enum CodingKeys: CodingKey {
case fields, values
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
fields = try container.decode([String].self, forKey: .fields)
rows = []
var rowsContainer = try container.nestedUnkeyedContainer(forKey: .values)
while !rowsContainer.isAtEnd {
var rowContainer = try rowsContainer.nestedUnkeyedContainer()
let row = (repeat try rowContainer.decode((each Result).self))
rows.append(row)
}
}
}
}
For the JSON shown above as an example, I could call JSONDecoder.decode
with the type NJQueryResponse<String, [String: Int]>
.
However, there is an error at the declaration of CodingKeys
.
Enums cannot declare a type pack
This makes me confused. CodingKeys
does not have a type pack.
How can I fix this error?
This has been reported as a bug on the Swift repo - #72069. The compiler seems to think that the each Result
type pack is declared by the CodingKeys
enum.
A workaround is to move the enum outside of NJQueryResponse
.
Note that as part of the Decodable
conformance synthesis for the outer NJQueryResponse
struct, a CodingKeys
enum (for the data
key) is also generated. However, because of the compiler bug, this will also not compile. This means that you will need to manually implement Decodable
for NJQueryResponse
too, not just NJQueryResponse.Data
.
Final code:
public struct NJQueryResponse<each Result: Decodable & Sendable>: Decodable, Sendable {
public let data: Data
public struct Data: Decodable, Sendable {
public let fields: [String]
public var rows: [(repeat each Result)]
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: NJQueryResponseDataCodingKeys.self)
fields = try container.decode([String].self, forKey: .fields)
rows = []
var rowsContainer = try container.nestedUnkeyedContainer(forKey: .values)
while !rowsContainer.isAtEnd {
var rowContainer = try rowsContainer.nestedUnkeyedContainer()
let row = (repeat try rowContainer.decode((each Result).self))
rows.append(row)
}
}
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: NJQueryResponseCodingKeys.self)
self.data = try container.decode(NJQueryResponse<repeat each Result>.Data.self, forKey: .data)
}
}
private enum NJQueryResponseCodingKeys: CodingKey {
case data
}
private enum NJQueryResponseDataCodingKeys: CodingKey {
case fields, values
}