I have the following Swift code with some sample JSON that I'm trying to decode. You can drop this into a Playground to try it for yourself:
let json = """
{
"sessionState": "abc",
"methodResponses": [
[
"Mailbox/get",
{
"state": "92",
"accountId": "xyz"
},
"0"
]
]
}
"""
let data = json.data(using: .utf8)!
if let object = try? JSONDecoder().decode(JMAPResponse.self, from: data) {
print(object)
}else{
print("JMAP decode failed")
}
I had no idea how to handle the nested array, so I used quicktype.io, but what it generated still doesn't work.
Here is my root element with the nested array:
//Root
struct JMAPResponse: Codable{
var sessionState: String?
var methodResponses: [[JMAPResponseChild]]
}
Here is the enum
that QuickType suggested I use to process the methodResponse
node:
//Nested array wrapper
enum JMAPResponseChild: Codable{
case methodResponseClass(JMAPMethodResponse)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(JMAPMethodResponse.self) {
self = .methodResponseClass(x)
return
}
throw DecodingError.typeMismatch(JMAPResponseChild.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JMAPResponseChild"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .methodResponseClass(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
Here's the next level down:
//No-key data structure (Mailbox/get, {}, 0)
struct JMAPMethodResponse: Codable{
var method: String
var data: [JMAPMailboxList]
var id: String
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(method)
try container.encode(data)
try container.encode(id)
}
}
And finally, the lowest-level node:
struct JMAPMailboxList: Codable{
var state: String?
var accountId: String?
}
It still can't decode the structure successfully. Can anyone see what I'm doing wrong?
CodePudding user response:
Your JMAPResponseChild
is decoding an array expecting either a String
or JMAPMethodResponse
. The array looks like this:
[
"Mailbox/get",
{
"state": "92",
"accountId": "xyz"
},
"0"
]
It actually contains either a String
or JMAPMailboxList
(not JMAPMethodResponse
). If you change all the JMAPMethodResponse
references in JMAPResponseChild
to JMAPMailboxList
it should work. You will have to do some post processing or change your code to translate that array of three values into JMAPMethodResponse
.
You might structure your JMapMethodResponse
more like this:
struct JMAPMethodResponse: Codable {
var method: String
var data: JMAPMailboxList
var id: String
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let values = try container.decode([JMAPResponseChild].self)
guard case .string(let extractedMethod) = values[0] else {
throw DecodingError.typeMismatch(String.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "First array element not String"))
}
method = extractedMethod
guard case .methodResponseClass(let extractedData) = values[1] else {
throw DecodingError.typeMismatch(JMAPMailboxList.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Second array element not JMAPMailboxList"))
}
data = extractedData
guard case .string(let extractedId) = values[2] else {
throw DecodingError.typeMismatch(String.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Third array element not String"))
}
id = extractedId
}
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(JMAPResponseChild.string(method))
try container.encode(JMAPResponseChild.methodResponseClass(data))
try container.encode(JMAPResponseChild.string(id))
}
}
Then you can just get an array of those in your response:
struct JMAPResponse: Codable{
var sessionState: String?
var methodResponses: [JMAPMethodResponse]
}
I don't know if the encoder or decoder guarantee ordering in the array. If they don't this could randomly break.