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