简体   繁体   English

如何在Swift4中验证此JSON?

[英]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对象而不必为其提供statusdata属性?

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成为StringRawRepresentable ,然后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 ,并且由于字段是StringInt ,因此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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM