JSONDecoder возвращает ноль при разборе

У меня есть следующая функция:

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 байта», так что явно что-то пошло не так перед декодированием, верно?

Обновлено: по запросу Уди вот структура 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: по предложению Роба я попробовал 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: "Нет значения, связанного с ключом CodingKeys(stringValue: "companies_skip", intValue: nil) ("companies_skip").", baseError: nil))

Покажите пример ваших данных json, а также структуру welcome

udi 17.01.2023 07:25

@udi только что обновил его как структурой, так и образцом ответа :)

Mansidak 17.01.2023 07:31

@Rob Только что обновил его. Это тебе о чем-нибудь говорит? :/

Mansidak 17.01.2023 07:43

то, что вы показываете, не является данными JSON.

workingdog support Ukraine 17.01.2023 07:46

имена ключей в данных JSON не соответствуют именам свойств в структуре Welcome .

udi 17.01.2023 07:47

@workingdogsupportUkraine, но это то, что я получаю, когда печатаю (ответ) :/

Mansidak 17.01.2023 07:47

то, что вы показываете сейчас, после вашего редактирования, это json. Скопируйте и вставьте этот настоящий json в app.quicktype.io. Затем замените JSONNull? на String?. Расшифровка, которая работает для меня. Обратите внимание, что то, что вы ожидаете получить, и то, что вы на самом деле получаете от сервера, может сильно отличаться (например, сообщение об ошибке в json).

workingdog support Ukraine 17.01.2023 08:16

добавьте print("--> response: \(String(data: json, encoding: .utf8))") после вашего print(json) // 2479 Bytes и покажите нам, что он печатает.

workingdog support Ukraine 17.01.2023 08:35

Как я сказал в вашем другом вопросе, используйте responseDecodable для декодирования JSON непосредственно в структуры. И если вы расшифровали материал вручную с помощью JSONDecoder, по крайней мере, printerror, а не бессмысленный строковый литерал.

vadian 17.01.2023 08:55

@workingdogsupportUkraine, поэтому я получил новую структуру из QuickType и заменил JSONNull? со строкой? (следовательно, также удаляя класс JSONNull), и я выполнил печать print("--> response: (String (data: json, encoding: .utf8))"), и я получаю ответ json в виде строки, обернутой как ответ: Необязательно ("{\"statusCode\":200,\"messageCode\":\"успех\",\"‌​результат\": ....} Это помогает?

Mansidak 17.01.2023 14:58

@Rob Мой плохой. Я попытался поймать ошибку и получил следующее: keyNotFound(CodingKeys(stringValue: "companies_skip", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), CodingKeys(stringValue: "inputParameters", intValue: nil)], debugDescription: "Нет значения, связанного с ключом CodingKeys (stringValue: \"companies_skip\", intValue: nil) (\"companies_skip\").", baseError: nil))

Mansidak 17.01.2023 15:00
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
0
11
123
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Сообщается, что ваша ошибка:

keyNotFound(CodingKeys(stringValue: "companies_skip", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), CodingKeys(stringValue: "inputParameters", intValue: nil)] , debugDescription: "Нет значения, связанного с ключом CodingKeys(stringValue: "companies_skip", intValue: nil) ("companies_skip").", baseError: nil))

Это указывает вам точно на то место, где расшифровка не удалась. По-видимому, в companies_skip » result нет ключа с именем inputParameters. Теперь вы не показываете нам полный ответ, который вы фактически получили, поэтому трудно быть точным. Но из этой ошибки мы можем сделать вывод, что ответ не совсем соответствует вашему образцу JSON, а ключ companies_skip отсутствует.

Из имени inputParameters мы можем сделать вывод, что ваш URL-адрес запроса (который, опять же, вы нам не предоставили), возможно, должен предоставить этот параметр. Или, в качестве альтернативы, возможно, это имя параметра не следует помечать как обязательный подключ структуры inputParameters (например, вы можете сделать его необязательным).

Независимо от деталей, это процесс. Если декодирование не удалось, посмотрите на полный объект ошибки, и он сообщит вам, где возникли проблемы. Обратите внимание: если есть несколько проблем с декодированием, ошибка будет сообщать только о первой, поэтому не удивляйтесь, если для решения всех проблем потребуется несколько раз и разные запросы. В первый раз, когда вы начинаете декодировать конкретный запрос, устранение всех потенциальных несоответствий может быть итеративным процессом.

Ответ принят как подходящий

Строковый ответ, который вы показываете в своем комментарии, означает, что вы получаете действительный ответ от сервера, и поэтому вы должны быть в состоянии расшифровать его со следующими моделями.

Используйте ответ @vadian на свой предыдущий вопрос: Невозможно правильно разобрать данные JSON из 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"
    }
}

Другие вопросы по теме