具有嵌套空数组的 JSON 的可编码 Swift 结构

Codable Swift Structs for JSON with Nested Empty Array

我有以下 Swift 代码和我正在尝试解码的一些示例 JSON。您可以将其放入 Playground 中亲自尝试:

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 used quicktype.io,但它生成的仍然不起作用。

这是我的嵌套数组的根元素:

//Root
struct JMAPResponse: Codable{
  var sessionState: String?
  var methodResponses: [[JMAPResponseChild]]
}

这是 QuickType 建议我用来处理 methodResponse 节点的 enum

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

这是下一层:

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

最后,最低级别的节点:

struct JMAPMailboxList: Codable{
  var state: String?
  var accountId: String?
}

仍然无法成功解码结构。谁能看出我做错了什么?

您的 JMAPResponseChild 正在解码需要 StringJMAPMethodResponse 的数组。该数组如下所示:

    [
      "Mailbox/get",
      {
        "state": "92",
        "accountId": "xyz"
      },
      "0"
    ]

它实际上包含 StringJMAPMailboxList(不是 JMAPMethodResponse)。如果将 JMAPResponseChild 中的所有 JMAPMethodResponse 引用更改为 JMAPMailboxList,它应该可以工作。您将必须进行一些 post 处理或更改您的代码以将该三个值的数组转换为 JMAPMethodResponse.

您的 JMapMethodResponse 结构可能更像这样:

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

然后你可以在你的回复中得到一个数组:

struct JMAPResponse: Codable{
  var sessionState: String?
  var methodResponses: [JMAPMethodResponse]
}

我不知道编码器或解码器是否保证数组中的顺序。如果他们不这样做,这可能会随机中断。