[英]How to validate this JSON in Swift4?
There is an API, which wraps its responses in an associative array with a status
value and a data
value, where data
contains either an error object, or the expected values: 有一个API,将其响应包装在具有
status
值和data
值的关联数组中,其中data
包含错误对象或期望值:
Bad Response: 错误回应:
{
"status":"error",
"data":{
"errormessage":"Duplicate entry '101' for key 'PRIMARY'",
"errorcode":1062
}
}
Successful response: 成功回应:
{
"status":"success",
"data":{
"user": {
"id": 1,
}
}
}
I want to validate these responses: 我想验证以下响应:
public class func validateResponse(_ data : Data) -> WebServicesError?
{
struct WTPResponse : Decodable
{
let status : String
let data : Data
}
do {
let response = try JSONDecoder().decode(WTPResponse.self, from: data) // FAILS HERE
if let wtpError = try? JSONDecoder().decode(WTPAPIError.self, from: response.data) {
return WebServicesError.wtpError(WTPAPIError(code: wtpError.code, message: wtpError.message))
}
}
catch let error {
return WebServicesError.init(error: error)
}
return nil
}
It always fails when trying to decode the response object with the error: Expected to decode Data but found a dictionary instead.
当尝试对错误的响应对象进行解码时,它总是失败:
Expected to decode Data but found a dictionary instead.
I was thinking that I could decode the data
object as the Swift type Data
, but it is really a [String: Any]
dictionary. 我当时以为我可以将
data
对象解码为Swift类型的Data
,但这实际上是[String: Any]
字典。
1) How can I validate the Data
I receive from the API? 1)如何验证从API收到的
Data
?
2) Is there a way I can extract only the " data
" portion of the JSON response as the Data
type, so that I can decode the User
object without having to give it a status
and data
properties? 2)有没有一种方法可以仅将JSON响应的“
data
”部分提取为Data
类型,以便可以解码User
对象而不必为其提供status
和data
属性?
As the other answers state, you essentially can't do this with JSONDecoder
because you can't decode a Data
for your "data"
key. 正如其他答案所述,您基本上无法使用
JSONDecoder
做到这一点,因为您无法为"data"
键解码Data
。 You'd need to decode it as a Dictionary<String, Any>
or something. 您需要将其解码为
Dictionary<String, Any>
东西。 I can think of a way to do that, but it's pretty cumbersome, and even then, you end up with a Dictionary
, not a Data
, so you'd have to re-encode it to get a Data
to pass to a JSONDecoder
later. 我可以想到一种方法,但是这样做很麻烦,即使那样,您最终还是使用
Dictionary
而不是Data
,所以您必须重新编码它才能使Data
稍后传递给JSONDecoder
。
Maybe this means you have to drop down to the lower-level JSONSerialization
and poke through the dictionaries “by hand”. 也许这意味着您必须下降到较低级别的
JSONSerialization
并“手动”浏览字典。 But if you know at decode-time exactly what kinds of responses you are looking for, then I suggest you work with the Swift Decodable
system instead of bypassing it. 但是,如果您在解码时确切地知道要寻找哪种响应,那么我建议您使用Swift
Decodable
系统,而不要绕过它。
At the top level, you have a response, which can either be a failure or a success, and carries a different data payload in each case. 在最高级别,您有一个响应,可以是失败或成功,并且在每种情况下都携带不同的数据有效负载。 That sounds like a Swift
enum
with associated values: 这听起来像带有关联值的Swift
enum
:
enum WTPResponse {
case failure(WTPFailure)
case success(WTPSuccess)
}
We want this to be decodable directly from the JSON, but we'll have to write the Decodable
conformance by hand. 我们希望它可以直接从JSON解码,但是我们必须手动编写
Decodable
一致性。 The compiler can't do it automatically for an enum
with associated values. 对于具有关联值的
enum
,编译器无法自动执行。 Before we write the Decodable
conformance, let's define all the other types we'll need. 在编写
Decodable
一致性之前,让我们定义我们需要的所有其他类型。
The type of response is identified by either the string "error"
or the string "success"
, which sounds like another Swift enum
. 响应的类型由字符串
"error"
或字符串"success"
标识,听起来像另一个Swift enum
。 We can make this enum
a RawRepresentable
of String
, and then Swift can make it Decodable
for us: 我们可以使这个
enum
成为String
的RawRepresentable
,然后Swift可以使它成为Decodable
:
enum WTPStatus: String, Decodable {
case error
case success
}
For the failure response type, the data payload has two fields. 对于故障响应类型,数据有效负载具有两个字段。 This sounds like a Swift
struct
, and since the fields are String
and Int
, Swift can make it Decodable
for us: 这听起来像Swift的
struct
,并且由于字段是String
和Int
,因此Swift可以为我们提供Decodable
功能:
struct WTPFailure: Decodable {
var errormessage: String
var errorcode: Int
}
For the success response type, the data payload is a user, which has an id: Int
field. 对于成功响应类型,数据有效载荷是一个用户,该用户具有一个
id: Int
字段。 This sounds like two Swift struct
s, which Swift can make Decodable
for us: 这听起来像是两个Swift
struct
,Swift可以为我们提供Decodable
功能:
struct WTPSuccess: Decodable {
var user: WTPUser
}
struct WTPUser: Decodable {
var id: Int
}
This covers everything that appears in your example JSONs. 这涵盖了示例JSON中出现的所有内容。 Now we can make
WTPResponse
conform to Decodable
by hand, like this: 现在,我们可以
WTPResponse
使WTPResponse
符合Decodable
,如下所示:
extension WTPResponse: Decodable {
enum CodingKeys: String, CodingKey {
case status
case data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch try container.decode(WTPStatus.self, forKey: .status) {
case .error: self = .failure(try container.decode(WTPFailure.self, forKey: .data))
case .success: self = .success(try container.decode(WTPSuccess.self, forKey: .data))
}
}
}
Here's a test: 这是一个测试:
let failureJsonString = """
{
"status":"error",
"data":{
"errormessage":"Duplicate entry '101' for key 'PRIMARY'",
"errorcode":1062
}
}
"""
let successJsonString = """
{
"status":"success",
"data":{
"user": {
"id": 1,
}
}
}
"""
let decoder = JSONDecoder()
do {
print(try decoder.decode(WTPResponse.self, from: failureJsonString.data(using: .utf8)!))
print(try decoder.decode(WTPResponse.self, from: successJsonString.data(using: .utf8)!))
} catch {
print(error)
}
And here's the output: 这是输出:
failure(test.WTPFailure(errormessage: "Duplicate entry \'101\' for key \'PRIMARY\'", errorcode: 1062))
success(test.WTPSuccess(user: test.WTPUser(id: 1)))
This is a case where Swift4 Codable doesn't work. 这是Swift4 Codable无法使用的情况。 You have to parse the JSON manually and take care of the cases.
您必须手动解析JSON并注意各种情况。 https://github.com/SwiftyJSON/SwiftyJSON
https://github.com/SwiftyJSON/SwiftyJSON
I'm not sure how you'd go about doing this with the new Codable
feature, as shayegh says. 正如shayegh所说,我不确定您将如何使用新的
Codable
功能来做到这一点。
You could instead use the JSONSerialization
class. 您可以改用
JSONSerialization
类。 That would convert your JSON data to a Dictionary that contains other dictionaries. 这样会将您的JSON数据转换为包含其他字典的Dictionary。 You could then interrogate the dictionary yourself through code.
然后,您可以自己通过代码查询字典。
That would be pretty easy. 那将很容易。
I used quicktype's multi-source mode to generate separate Codable
models for each response type: 我使用quicktype的多源模式为每种响应类型生成单独的
Codable
模型:
And here's the code. 这是代码。 You can try to decode a
Response
first, and, if that fails, you can try to decode a BadResponse
. 您可以先尝试解码
Response
,如果失败,则可以尝试解码BadResponse
。
// let response = try? JSONDecoder().decode(Response.self, from: jsonData)
// let badResponse = try? JSONDecoder().decode(BadResponse.self, from: jsonData)
import Foundation
struct Response: Codable {
let status: String
let data: ResponseData
}
struct ResponseData: Codable {
let user: User
}
struct BadResponse: Codable {
let status: String
let data: BadResponseData
}
struct BadResponseData: Codable {
let errormessage: String
let errorcode: Int
}
struct User: Codable {
let id: Int
}
I think this is a bit neater than trying to express this as a single type. 我认为这比尝试将其表示为单一类型稍微整洁。 I also suggest not selectively decoding the JSON, and rather decoding all of it, then picking out the data you want from these types.
我还建议不要选择性地解码JSON,而是解码所有JSON,然后从这些类型中选择所需的数据。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.