Home > Software engineering >  JSONDecoder returning nil while parsing
JSONDecoder returning nil while parsing

Time:01-18

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/20221031/us-east-1/s3/aws4_request&X-Amz-Date=20221031T185642Z&X-Amz-Expires=1800&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJP//////////wEaCXVzLWVhc3QtMSJIMEYCIQDfX8VNAl5kBgttrCU85U5wc1ZtSOmshO6/PilXOv8nvgIhAIveFfsk+2CnEkrMZWriodEPsj0osO5a5zV6eVu/XfuZKp8DCHwQAhoMMDQ1NTU3NzA4OTA3IgyrbhVK0MP1WMFBXh0q/AJulP5qfaV5mn3NRbINqZN4hy4Dg3IujNrZjw8ef32sWE1Gj2D/c0YTJUzvx/nm7LxyNO6AR35mrVy/Bm9Q80UIspkcLMl45EK/oUDO0fAvoUF8g6iZ905qS3MvnOTxXkObhM1PVmpFeJFMw3jksnOPfKE4X7Ut/JXNwD/5QzdkQCXkGem+lrYSSSf8jB8lihTAjT/NXmOKMv3jktmZ13T8J1R8F8zeuLPMQf7QphUzlKn5joPb28cConluQC97y+jwxqIYjvIFKXY9cZEoaHGh4c6FbXsia714zG3CQp8NSGLbqCCu93oJI1Z61E+Z6PhB3vZGdBvXi61AlJcxZ7sti6i0h4VAbWspiJIgWwoZzrsTtneBNNpUW9tvtacGgEZIwAKV/3AhVEZu3WC1eQ9HtfjT9/jW99SEB8VVGXwkM/A9mtT/uiL0cAfQZRMhtbQJXXDRdkYEw/WuhjJ3zxEtEB2m3uH++UEzOzGTd5Knm+ero+hMfN8X+otm3DDbtICbBjqcAf5Riii0XE1w2TZvpm/PNHTchCu7FnNz5hfvflv8scpgO5M4bGpy/adI4/7AUQqCQXFw4scF0FCCdb8AKJZsFGG18W1jjDHyR0YuxZFQ/JQRt0JP3yr+kVxjAH7qTtc0AzF/nGTgy3MOF+m6Y7EkyCWyV2r6o1JTBQMftlf7MI8Uvw4cSZE6JoZviaFtmKVLGGgR4F3cDiyU56augA==&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))

CodePudding user response:

Your error was reportedly:

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

That points you precisely to where the decoding failed. There is apparently no key called companies_skip in result » inputParameters. Now, you don't show us the full response you actually received, so it is hard to be precise. But we can infer from this error that the response does not precisely match your sample JSON, but rather, the companies_skip key is not present.

We might infer from the name, inputParameters, that your request URL (which, again, you have not shared with us) may possibly need to supply that parameter. Or, alternatively, perhaps that parameter name shouldn't be marked as a required sub key of the inputParameters structure (e.g., you might want to make it an optional).

Regardless of the particulars, this is the process. If decoding fails, look at the complete error object, and it will tell you where it had problems. Note, if there are multiple decoding problems, the error will only report the first one, so do not be surprised if it takes a few times and different queries to resolve all of the issues. The first time you start decoding a particular request, resolving all of the potential discrepancies may be an iterative process.

CodePudding user response:

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/20221031/us-east-1/s3/aws4_request&X-Amz-Date=20221031T185642Z&X-Amz-Expires=1800&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJP//////////wEaCXVzLWVhc3QtMSJIMEYCIQDfX8VNAl5kBgttrCU85U5wc1ZtSOmshO6/PilXOv8nvgIhAIveFfsk+2CnEkrMZWriodEPsj0osO5a5zV6eVu/XfuZKp8DCHwQAhoMMDQ1NTU3NzA4OTA3IgyrbhVK0MP1WMFBXh0q/AJulP5qfaV5mn3NRbINqZN4hy4Dg3IujNrZjw8ef32sWE1Gj2D/c0YTJUzvx/nm7LxyNO6AR35mrVy/Bm9Q80UIspkcLMl45EK/oUDO0fAvoUF8g6iZ905qS3MvnOTxXkObhM1PVmpFeJFMw3jksnOPfKE4X7Ut/JXNwD/5QzdkQCXkGem+lrYSSSf8jB8lihTAjT/NXmOKMv3jktmZ13T8J1R8F8zeuLPMQf7QphUzlKn5joPb28cConluQC97y+jwxqIYjvIFKXY9cZEoaHGh4c6FbXsia714zG3CQp8NSGLbqCCu93oJI1Z61E+Z6PhB3vZGdBvXi61AlJcxZ7sti6i0h4VAbWspiJIgWwoZzrsTtneBNNpUW9tvtacGgEZIwAKV/3AhVEZu3WC1eQ9HtfjT9/jW99SEB8VVGXwkM/A9mtT/uiL0cAfQZRMhtbQJXXDRdkYEw/WuhjJ3zxEtEB2m3uH++UEzOzGTd5Knm+ero+hMfN8X+otm3DDbtICbBjqcAf5Riii0XE1w2TZvpm/PNHTchCu7FnNz5hfvflv8scpgO5M4bGpy/adI4/7AUQqCQXFw4scF0FCCdb8AKJZsFGG18W1jjDHyR0YuxZFQ/JQRt0JP3yr+kVxjAH7qTtc0AzF/nGTgy3MOF+m6Y7EkyCWyV2r6o1JTBQMftlf7MI8Uvw4cSZE6JoZviaFtmKVLGGgR4F3cDiyU56augA==&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"
    }
}
  • Related