JSON 响应类型不同时解码失败

JSON decoding fails when response has different types

我在下面有一个 API 回复。 “USER_LIST”响应根据“DATA_NUM”的值而不同。
我遇到的问题是,当“DATA_NUM”为“0”时,returns 为空字符串,而当“DATA_NUM”为“1”时,“USER_LIST" returns 对象和空字符串,这样我就无法使用下面的模型进行解码。我想构建一个适用于每种情况的模型,而不管“DATA_NUM”的值如何。

我怎样才能做到这一点?提前致谢。

API 回应

// when "DATA_NUM": "0"
{
  "RESPONSE": {
    "DATA_NUM": "0",
    "USER_LIST": ""
  }
}

// when "DATA_NUM": "1"
{
  "RESPONSE": {
    "DATA_NUM": "1",
    "USER_LIST": [
     {
      "USER_NAME": "Jason",
      "USER_AGE": "30",
      "ID": "12345"
     },
     ""
  ]
 }
}

// when "DATA_NUM": "2"
{
  "RESPONSE": {
    "DATA_NUM": "2",
    "USER_LIST": [
     {
      "USER_NAME": "Jason",
      "USER_AGE": "30",
      "ID": "12345"
     },
     {
     "USER_NAME": "Amy",
      "USER_AGE": "24",
      "ID": "67890"
     }
   ]
 }
}

型号

struct UserDataResponse: Codable {
  let RESPONSE: UserData?
}

struct UserData: Codable {
  let DATA_NUM: String?
  let USER_LIST: [UserInfo]?
}

struct UserInfo: Codable {
  let USER_NAME: String?
  let USER_AGE: String?
  let ID: String?
}

解码

do {
  let res: UserDataResponse = try JSONDecoder().decode(UserDataResponse.self, from: data)
  guard let userData: UserData = res.RESPONSE else { return }
  print("Successfully decoded", userData)
} catch {
  print("failed to decode") // failed to decode when "DATA_NUM" is "0" or "1"
}

您可以编写此代码来解决此数组字符串问题。

struct UserDataResponse: Codable {
let RESPONSE: UserData?
}

struct UserData: Codable {
  let DATA_NUM: String?
  let USER_LIST: [UserInfo]?
    
    struct USER_LIST: Codable {
        var USER_LIST: CustomMetadataType
    }
}

enum CustomMetadataType: Codable {
    case array([String])
    case string(String)
init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    do {
        self = try .array(container.decode(Array.self))
    } catch DecodingError.typeMismatch {
        do {
            self = try .string(container.decode(String.self))
        } catch DecodingError.typeMismatch {
            throw DecodingError.typeMismatch(CustomMetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
        }
    }
}

func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    switch self {
    case .array(let array):
        try container.encode(array)
    case .string(let string):
        try container.encode(string)
    }
    }
}

struct UserInfo: Codable {
  let USER_NAME: String?
  let USER_AGE: String?
  let ID: String?
}

这是一个使用自定义 init(from:) 来处理奇怪 USER_LIST

的解决方案

结构用户数据响应:可解码{ 让回应:用户数据

  enum CodingKeys: String, CodingKey {
    case response = "RESPONSE"
  }
}

struct UserData: Decodable {
  let dataNumber: String
  let users: [UserInfo]
  
  enum CodingKeys: String, CodingKey {
    case dataNumber = "DATA_NUM"
    case users = "USER_LIST"
  }
  
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    dataNumber = try container.decode(String.self, forKey: .dataNumber)
    if let _ = try? container.decode(String.self, forKey: .users) {
      users = []
      return
    }
    
    var nestedContainer = try container.nestedUnkeyedContainer(forKey: .users)
    
    var temp: [UserInfo] = []
    do {
      while !nestedContainer.isAtEnd {
        let user = try nestedContainer.decode(UserInfo.self)
        temp.append(user)
      }
    } catch {}
    
    self.users = temp
  }
}

struct UserInfo: Decodable {
  let name: String
  let age: String
  let id: String
  
  enum CodingKeys: String, CodingKey {
    case name = "USER_NAME"
    case age = "USER_AGE"
    case id = "ID"
  }
}

一个示例(data1、data2、data3 对应问题中发布的 json 个示例)

let decoder = JSONDecoder()
for data in [data1, data2, data3] {
  do {
    let result = try decoder.decode(UserDataResponse.self, from: data)
    print("Response \(result.response.dataNumber)")
    print(result.response.users)
  } catch {
    print(error)
  }
}

输出

Response 0
[]
Response 1
[__lldb_expr_93.UserInfo(name: "Jason", age: "30", id: "12345")]
Response 2
[__lldb_expr_93.UserInfo(name: "Jason", age: "30", id: "12345"), __lldb_expr_93.UserInfo(name: "Amy", age: "24", id: "67890")]


使用 while 循环的替代解决方案进行编辑

在上面的代码中有一个 while 循环被 do/catch 包围,所以我们在抛出错误时立即退出循环,这工作正常,因为有问题的空字符串是最后一个json 数组中的元素。选择此解决方案是因为 nestedContainer 的迭代器是 而不是 如果解码失败则前进到下一个元素,因此只需对 do/catch 执行相反的操作(其中catch 子句为空)在循环内会导致无限循环。

另一个可行的解决方案是解码 catch 中的“”以推进迭代器。我不确定这里是否需要这样做,但如果空字符串位于数组中的其他位置而不是最后一个,解决方案会变得更加灵活。

备选循环:

while !nestedContainer.isAtEnd {
  do {
    let user = try nestedContainer.decode(UserInfo.self)
    temp.append(user)
  } catch {
    _ = try! nestedContainer.decode(String.self)
  }
}