Search code examples
jsonswiftalamofirejsondecoder

JSONDecoder returning nil while parsing


I have the following 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)
        }
        
    }
}

Which is being called as follows:

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)

    }
}

But for some reason, my 'welcome' value always returns nil. Can anyone suggest what could've gone wrong? When I print(json) I'm getting '294 Bytes' for some reason so clearly something went wrong before decoding, right?

EDIT: Upon Udi's request here's the Welcome struct

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

and here's a sample of JSON response

{
  "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: Upon Rob's suggestion, I tried do-try-catch, as follows:

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

Which reports the 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))


Solution

  • The string response you show in your comment, means you get a valid response from the server, and so you should be able to decode it with the following models.

    Use @vadian answer to your previous question : Unable to parse JSON data properly from Alomafire

    Here are the test code and models to decode the response into a set of structs.

    Note you will have to consult the server doc to determine which properties are Optional and adjust the code (i,e put ?) where nessesary .

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