繁体   English   中英

JSONDecoder 在解析时返回 nil

[英]JSONDecoder returning nil while parsing

我有以下 function:

func executeGet( completion: @escaping (Data?, Error?) -> Void) {
    AF.request("https:URL",
               method:.get,
               headers:headers).response{ response in
        debugPrint(response)

     
        if let error = response.error {
            completion(nil, error)
        }
        else if let jsonArray = response.value as? Data{
            completion(jsonArray, nil)
        }
        
    }
}

其调用如下:

executeGet() { (json, error) in
    if let error = error{
        print(error.localizedDescription)
        
    }
    else if let json = json {
        print(type(of:json))
        print(json)
        let welcome = try? JSONDecoder().decode(Welcome.self, from: json)
        print(welcome)

    }
}

但出于某种原因,我的“欢迎”值总是返回零。 任何人都可以建议可能出了什么问题吗? 当我print(json)时,出于某种原因我得到“294 字节”,很明显在解码之前出了点问题,对吧?

编辑:根据 Udi 的要求,这里是Welcome结构

// MARK: - Welcome
struct Welcome: Codable {
    let statusCode: Int
    let messageCode: String
    let result: Result
}

// MARK: - Result
struct Result: Codable {
    let id: String
    let inputParameters: InputParameters
    let robotID: String
    let runByUserID, runByTaskMonitorID: JSONNull?
    let runByAPI: Bool
    let createdAt, startedAt, finishedAt: Int
    let userFriendlyError: JSONNull?
    let triedRecordingVideo: Bool
    let videoURL: String
    let videoRemovedAt: Int
    let retriedOriginalTaskID: String
    let retriedByTaskID: JSONNull?
    let capturedDataTemporaryURL: String
    let capturedTexts: CapturedTexts
    let capturedScreenshots: CapturedScreenshots
    let capturedLists: CapturedLists
    
    enum CodingKeys: String, CodingKey {
        case id, inputParameters
        case robotID = "robotId"
        case runByUserID = "runByUserId"
        case runByTaskMonitorID = "runByTaskMonitorId"
        case runByAPI, createdAt, startedAt, finishedAt, userFriendlyError, triedRecordingVideo
        case videoURL = "videoUrl"
        case videoRemovedAt
        case retriedOriginalTaskID = "retriedOriginalTaskId"
        case retriedByTaskID = "retriedByTaskId"
        case capturedDataTemporaryURL = "capturedDataTemporaryUrl"
        case capturedTexts, capturedScreenshots, capturedLists
    }
}

// MARK: - CapturedLists
struct CapturedLists: Codable {
    let companies: [Company]
}

// MARK: - Company
struct Company: Codable {
    let position, name, location, description: String
    
    enum CodingKeys: String, CodingKey {
        case position = "Position"
        case name, location, description
    }
}

// MARK: - CapturedScreenshots
struct CapturedScreenshots: Codable {
}

// MARK: - CapturedTexts
struct CapturedTexts: Codable {
    let productName, width, patternRepeat, construction: String
    let fiber: String
    let color: JSONNull?
    let mainImage: String
    
    enum CodingKeys: String, CodingKey {
        case productName = "Product Name"
        case width = "Width"
        case patternRepeat = "Pattern Repeat"
        case construction = "Construction"
        case fiber = "Fiber"
        case color = "Color"
        case mainImage = "Main Image"
    }
}

// MARK: - InputParameters
struct InputParameters: Codable {
    let originURL: String
    let companiesSkip, companiesLimit: Int
    
    enum CodingKeys: String, CodingKey {
        case originURL = "originUrl"
        case companiesSkip = "companies_skip"
        case companiesLimit = "companies_limit"
    }
}

// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
    
    public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool
    {
        return true
    }
    
    public var hashValue: Int {
        return 0
    }
    
    public init() {}
    
    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if !container.decodeNil() {
            throw DecodingError.typeMismatch(JSONNull.self,  DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
        }
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encodeNil()
    }
}

这是 JSON 响应的示例

{
  "statusCode": 200,
  "messageCode": "success",
  "result": {
    "id": "f6fb62b6-f06a-4bf7-a623-c6a35c2e70b0",
    "inputParameters": {
      "originUrl": "https://www.ycombinator.com/companies/airbnb",
      "companies_skip": 0,
      "companies_limit": 10
    },
    "robotId": "4f5cd7ff-6c98-4cac-8cf0-d7d0cb050b06",
    "runByUserId": null,
    "runByTaskMonitorId": null,
    "runByAPI": true,
    "createdAt": 1620739118,
    "startedAt": 1620739118,
    "finishedAt": 1620739118,
    "userFriendlyError": null,
    "triedRecordingVideo": true,
    "videoUrl": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/system-1620230966-b1a9688b-05d3-4682-beeb-9ce035e482b1.mp4",
    "videoRemovedAt": 1620739118,
    "retriedOriginalTaskId": "673da019-bf0c-476e-9c4f-d35252a151dc",
    "retriedByTaskId": null,
    "capturedDataTemporaryUrl": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/system-1620230966-b1a9688b-05d3-4682-beeb-9ce035e482b1.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAQVG3TPBVXHSCAX63%2F20221031%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221031T185642Z&X-Amz-Expires=1800&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDfX8VNAl5kBgttrCU85U5wc1ZtSOmshO6%2FPilXOv8nvgIhAIveFfsk%2B2CnEkrMZWriodEPsj0osO5a5zV6eVu%2FXfuZKp8DCHwQAhoMMDQ1NTU3NzA4OTA3IgyrbhVK0MP1WMFBXh0q%2FAJulP5qfaV5mn3NRbINqZN4hy4Dg3IujNrZjw8ef32sWE1Gj2D%2Fc0YTJUzvx%2Fnm7LxyNO6AR35mrVy%2FBm9Q80UIspkcLMl45EK%2FoUDO0fAvoUF8g6iZ905qS3MvnOTxXkObhM1PVmpFeJFMw3jksnOPfKE4X7Ut%2FJXNwD%2F5QzdkQCXkGem%2BlrYSSSf8jB8lihTAjT%2FNXmOKMv3jktmZ13T8J1R8F8zeuLPMQf7QphUzlKn5joPb28cConluQC97y%2BjwxqIYjvIFKXY9cZEoaHGh4c6FbXsia714zG3CQp8NSGLbqCCu93oJI1Z61E%2BZ6PhB3vZGdBvXi61AlJcxZ7sti6i0h4VAbWspiJIgWwoZzrsTtneBNNpUW9tvtacGgEZIwAKV%2F3AhVEZu3WC1eQ9HtfjT9%2FjW99SEB8VVGXwkM%2FA9mtT%2FuiL0cAfQZRMhtbQJXXDRdkYEw%2FWuhjJ3zxEtEB2m3uH%2B%2BUEzOzGTd5Knm%2Bero%2BhMfN8X%2Botm3DDbtICbBjqcAf5Riii0XE1w2TZvpm%2FPNHTchCu7FnNz5hfvflv8scpgO5M4bGpy%2FadI4%2F7AUQqCQXFw4scF0FCCdb8AKJZsFGG18W1jjDHyR0YuxZFQ%2FJQRt0JP3yr%2BkVxjAH7qTtc0AzF%2FnGTgy3MOF%2Bm6Y7EkyCWyV2r6o1JTBQMftlf7MI8Uvw4cSZE6JoZviaFtmKVLGGgR4F3cDiyU56augA%3D%3D&X-Amz-Signature=a7bb4d7597ad37cdf1f260890c3c474f7f49334db58c9650d75302a34126f7bc&X-Amz-SignedHeaders=host",
    "capturedTexts": {
      "Product Name": "Alexis",
      "Width": "15",
      "Pattern Repeat": "PATTERN REPEAT",
      "Construction": "Hand woven",
      "Fiber": "100% Wool",
      "Color": null,
      "Main Image": "https://isteam.wsimg.com/ip/e31f7bba-252b-4669-9209-639d1c00765d/ols/258_original"
    },
    "capturedScreenshots": {
      "top-ads": {
        "id": "b4d132f3-12d9-4770-ac7d-88e481fc5b47",
        "name": "Top ads",
        "src": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/00001-user-1620230947-6f113cf2-90ef-4c66-a448-9d5c6bd64873.png",
        "width": 600,
        "height": 120,
        "x": 201,
        "y": 142,
        "deviceScaleFactor": 1.2,
        "full": "page",
        "comparedToScreenshotId": "29d742c2-6f45-4f29-9d48-ba6fe66e6e3d",
        "diffImageSrc": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/00001-user-1620230947-6f113cf2-90ef-4c66-a448-9d5c6bd64873.png",
        "changePercentage": 20,
        "diffThreshold": 5,
        "fileRemovedAt": 1620739118
      }
    },
    "capturedLists": {
      "companies": [
        {
          "Position": "1",
          "name": "Airbnb",
          "location": "San Francisco, CA, USA",
          "description": "Book accommodations around the world."
        },
        {
          "Position": "2",
          "name": "Coin base",
          "location": "San Francisco, CA, USA",
          "description": "Buy, sell, and manage crypto currencies."
        },
        {
          "Position": "3",
          "name": "DoorDash",
          "location": "San Francisco, CA, USA",
          "description": "Restaurant delivery."
        }
      ]
    }
  }
}

EDIT2:根据 Rob 的建议,我尝试了do - try - catch ,如下所示:

executeGet() { (json, error) in
    if let error = error{
        print(error.localizedDescription)
        
    }
    else if let json = json {
        print(type(of:json)) // Data
        print(json)   // 2479 Bytes
        do{
            var welcome = try JSONDecoder().decode(Welcome.self, from: json)
            print(welcome)
        }
        catch {
            print(error)
        }
        
    }
}

其中报告错误:

keyNotFound(CodingKeys(stringValue: "companies_skip", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), CodingKeys(stringValue: "inputParameters", intValue: nil)] , debugDescription: "No value associated with key CodingKeys(stringValue: "companies_skip", intValue: nil) ("companies_skip").", underlyingError: nil))

据报道你的错误是:

keyNotFound(CodingKeys(stringValue: "companies_skip", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), CodingKeys(stringValue: "inputParameters", intValue: nil)] , debugDescription: "No value associated with key CodingKeys(stringValue: "companies_skip", intValue: nil) ("companies_skip").", underlyingError: nil))

这准确地指出了解码失败的地方。 result » inputParameters中显然没有名为companies_skip的键。 现在,你没有向我们展示你实际收到的完整回复,所以很难准确。 但是我们可以从这个错误中推断出响应与您的样本 JSON 不完全匹配,而是companies_skip键不存在。

我们可能会从名称inputParameters推断出您的请求 URL(同样,您没有与我们共享)可能需要提供该参数。 或者,也许该参数名称不应标记为inputParameters结构的必需子键(例如,您可能希望将其设为可选)。

不管细节如何,这就是过程。 如果解码失败,看完整的错误object,它会告诉你哪里出了问题。 注意,如果有多个解码问题,错误只会报告第一个,所以如果需要几次不同的查询来解决所有问题,请不要感到惊讶。 第一次开始解码特定请求时,解决所有潜在差异可能是一个迭代过程。

您在评论中显示的字符串响应意味着您从服务器获得了有效响应,因此您应该能够使用以下模型对其进行解码。

使用@vadian 回答你之前的问题: Unable to parse JSON data properly from Alomafire

以下是将响应解码为一组结构的测试代码和模型。

请注意,您必须查阅服务器文档以确定哪些属性是Optional的,并在必要时调整代码(即放置? )。

struct ContentView: View {
    @State var welcome: WelcomeResponse?
    
    var body: some View {
        VStack {
            if let response = welcome {
                Text(response.messageCode)
                Text("\(response.statusCode)")
                ForEach(response.result.capturedLists.companies) { item in
                    Text(item.description)
                }
            }
        }
        .onAppear {
            let json = """
            {
             "statusCode": 200,
              "messageCode": "success",
             "result": {
              "id": "f6fb62b6-f06a-4bf7-a623-c6a35c2e70b0",
              "inputParameters": {
              "originUrl": "https://www.ycombinator.com/companies/airbnb",
              "companies_skip": 0,
              "companies_limit": 10
            },
            "robotId": "4f5cd7ff-6c98-4cac-8cf0-d7d0cb050b06",
            "runByUserId": null,
            "runByTaskMonitorId": null,
            "runByAPI": true,
            "createdAt": 1620739118,
            "startedAt": 1620739118,
            "finishedAt": 1620739118,
            "userFriendlyError": null,
            "triedRecordingVideo": true,
            "videoUrl": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/system-1620230966-b1a9688b-05d3-4682-beeb-9ce035e482b1.mp4",
            "videoRemovedAt": 1620739118,
                "retriedOriginalTaskId": "673da019-bf0c-476e-9c4f-d35252a151dc",
                "retriedByTaskId": null,
                "capturedDataTemporaryUrl": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/system-1620230966-b1a9688b-05d3-4682-beeb-9ce035e482b1.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAQVG3TPBVXHSCAX63%2F20221031%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221031T185642Z&X-Amz-Expires=1800&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDfX8VNAl5kBgttrCU85U5wc1ZtSOmshO6%2FPilXOv8nvgIhAIveFfsk%2B2CnEkrMZWriodEPsj0osO5a5zV6eVu%2FXfuZKp8DCHwQAhoMMDQ1NTU3NzA4OTA3IgyrbhVK0MP1WMFBXh0q%2FAJulP5qfaV5mn3NRbINqZN4hy4Dg3IujNrZjw8ef32sWE1Gj2D%2Fc0YTJUzvx%2Fnm7LxyNO6AR35mrVy%2FBm9Q80UIspkcLMl45EK%2FoUDO0fAvoUF8g6iZ905qS3MvnOTxXkObhM1PVmpFeJFMw3jksnOPfKE4X7Ut%2FJXNwD%2F5QzdkQCXkGem%2BlrYSSSf8jB8lihTAjT%2FNXmOKMv3jktmZ13T8J1R8F8zeuLPMQf7QphUzlKn5joPb28cConluQC97y%2BjwxqIYjvIFKXY9cZEoaHGh4c6FbXsia714zG3CQp8NSGLbqCCu93oJI1Z61E%2BZ6PhB3vZGdBvXi61AlJcxZ7sti6i0h4VAbWspiJIgWwoZzrsTtneBNNpUW9tvtacGgEZIwAKV%2F3AhVEZu3WC1eQ9HtfjT9%2FjW99SEB8VVGXwkM%2FA9mtT%2FuiL0cAfQZRMhtbQJXXDRdkYEw%2FWuhjJ3zxEtEB2m3uH%2B%2BUEzOzGTd5Knm%2Bero%2BhMfN8X%2Botm3DDbtICbBjqcAf5Riii0XE1w2TZvpm%2FPNHTchCu7FnNz5hfvflv8scpgO5M4bGpy%2FadI4%2F7AUQqCQXFw4scF0FCCdb8AKJZsFGG18W1jjDHyR0YuxZFQ%2FJQRt0JP3yr%2BkVxjAH7qTtc0AzF%2FnGTgy3MOF%2Bm6Y7EkyCWyV2r6o1JTBQMftlf7MI8Uvw4cSZE6JoZviaFtmKVLGGgR4F3cDiyU56augA%3D%3D&X-Amz-Signature=a7bb4d7597ad37cdf1f260890c3c474f7f49334db58c9650d75302a34126f7bc&X-Amz-SignedHeaders=host",
            "capturedTexts": {
              "Product Name": "Alexis",
              "Width": "15",
              "Pattern Repeat": "PATTERN REPEAT",
              "Construction": "Hand woven",
              "Fiber": "100% Wool",
              "Color": null,
              "Main Image": "https://isteam.wsimg.com/ip/e31f7bba-252b-4669-9209-639d1c00765d/ols/258_original"
            },
            "capturedScreenshots": {
              "top-ads": {
                "id": "b4d132f3-12d9-4770-ac7d-88e481fc5b47",
                "name": "Top ads",
                "src": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/00001-user-1620230947-6f113cf2-90ef-4c66-a448-9d5c6bd64873.png",
                "width": 600,
                "height": 120,
                "x": 201,
                "y": 142,
                "deviceScaleFactor": 1.2,
                "full": "page",
                "comparedToScreenshotId": "29d742c2-6f45-4f29-9d48-ba6fe66e6e3d",
                "diffImageSrc": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/00001-user-1620230947-6f113cf2-90ef-4c66-a448-9d5c6bd64873.png",
                "changePercentage": 20,
                "diffThreshold": 5,
                "fileRemovedAt": 1620739118
              }
            },
            "capturedLists": {
              "companies": [
                {
                  "Position": "1",
                  "name": "Airbnb",
                  "location": "San Francisco, CA, USA",
                  "description": "Book accommodations around the world."
                },
                {
                  "Position": "2",
                  "name": "Coin base",
                  "location": "San Francisco, CA, USA",
                  "description": "Buy, sell, and manage crypto currencies."
                },
                {
                  "Position": "3",
                  "name": "DoorDash",
                  "location": "San Francisco, CA, USA",
                  "description": "Restaurant delivery."
                }
              ]
                }
                }
                 }
            """
            // simulated API data from the server
            let data = json.data(using: .utf8)!
            do {
                let results = try JSONDecoder().decode(WelcomeResponse.self, from: data)
                welcome = results
                print("\n---> results: \(results) \n")
            } catch {
                print("\n---> decoding error: \n \(error)\n")
            }
        }
    }
}

// MARK: - WelcomeResponse
struct WelcomeResponse: Codable {
    let statusCode: Int
    let messageCode: String
    let result: Result
}

// MARK: - Result
struct Result: Codable {
    let id: String
    let inputParameters: InputParameters
    let robotID: String
    let runByUserID, runByTaskMonitorID: String?
    let runByAPI: Bool
    let createdAt, startedAt, finishedAt: Int
    let userFriendlyError: String?
    let triedRecordingVideo: Bool
    let videoURL: String
    let videoRemovedAt: Int
    let retriedOriginalTaskID: String
    let retriedByTaskID: String?
    let capturedDataTemporaryURL: String
    let capturedTexts: CapturedTexts
    let capturedScreenshots: CapturedScreenshots
    let capturedLists: CapturedLists
    
    enum CodingKeys: String, CodingKey {
        case id, inputParameters
        case robotID = "robotId"
        case runByUserID = "runByUserId"
        case runByTaskMonitorID = "runByTaskMonitorId"
        case runByAPI, createdAt, startedAt, finishedAt, userFriendlyError, triedRecordingVideo
        case videoURL = "videoUrl"
        case videoRemovedAt
        case retriedOriginalTaskID = "retriedOriginalTaskId"
        case retriedByTaskID = "retriedByTaskId"
        case capturedDataTemporaryURL = "capturedDataTemporaryUrl"
        case capturedTexts, capturedScreenshots, capturedLists
    }
}

// MARK: - CapturedLists
struct CapturedLists: Codable {
    let companies: [Company]
}

// MARK: - Company
struct Company: Identifiable, Codable {
    let id = UUID()
    let position, name, location, description: String
    
    enum CodingKeys: String, CodingKey {
        case position = "Position"
        case name, location, description
    }
}

// MARK: - CapturedScreenshots
struct CapturedScreenshots: Codable {
    let topAds: TopAds
    
    enum CodingKeys: String, CodingKey {
        case topAds = "top-ads"
    }
}

// MARK: - TopAds
struct TopAds: Codable {
    let id, name: String
    let src: String
    let width, height, x, y: Int
    let deviceScaleFactor: Double
    let full, comparedToScreenshotId: String
    let diffImageSrc: String
    let changePercentage, diffThreshold, fileRemovedAt: Int
}

// MARK: - CapturedTexts
struct CapturedTexts: Codable {
    let productName, width, patternRepeat, construction: String
    let fiber: String
    let color: String?
    let mainImage: String
    
    enum CodingKeys: String, CodingKey {
        case productName = "Product Name"
        case width = "Width"
        case patternRepeat = "Pattern Repeat"
        case construction = "Construction"
        case fiber = "Fiber"
        case color = "Color"
        case mainImage = "Main Image"
    }
}

// MARK: - InputParameters
struct InputParameters: Codable {
    let originUrl: String
    let companiesSkip: Int?
    let companiesLimit: Int?
    
    enum CodingKeys: String, CodingKey {
        case originUrl
        case companiesSkip = "companies_skip"
        case companiesLimit = "companies_limit"
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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