jsonswiftuialamofirejsondecoderencodable

Unable to parse JSON data properly from Alomafire


I know there are various threads on this but it seems like there's a fundamental flaw in my understanding of objects in Swift.

So I have the following function that returns a JSON response as expected:

   func executeGet( completion: @escaping ([[String:Any]]?, Error?) -> Void) {
                AF.request("https://myURL",
                           method:.get,
                           headers:headers).responseJSON{ response in
                    debugPrint(response) <- I get readable JSON response from here
                 
                    if let error = response.error {
                        completion(nil, error)
                    }
                    else if let jsonArray = response.value as? [[String:Any]]{
                        completion(jsonArray, nil)
                    }
                    else if let jsonDict = response.value as? [String:Any]{
                        completion([jsonDict], nil )
                    }
                    
                }
            }

And then I'm able to leverage the completion handler to read the JSON response outside of AL's scope:

            executeGet() { (json, error) in
                if let error = error{
                    print(error.localizedDescription)
                    
                }
                else if let json = json {
                    print(type(of:json)) <- did this for sanity check
                    print(json["result"][0]["capturedlists"]) <- Error No exact matches in call to subscript 
                }
            }

But I'm unable to just get the 'capturedLists' part of the JSON. The JSON snippet I need to capture looks as follows:

"capturedLists":{"Something":
    [{"Position":"1","A1":"a1","B1":"b1"},
    {"Position":"2","A2":"a2","B2":"b2"}]}}}

Can anyone help me figure out what am I doing wrong? I know usually you can get data from Json by something like response[a][0] but that doesn't work here for some reason.

EDIT: Upone suggestion I used Quicktype to get a struct for my response and got the following:

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

Solution

  • .responseJSON is deprecated. Don't use it.

    The error occurs because json["result"] returns Any but the compiler must know the static type for the subsequent index subscription.

    As you have a Decodable model AF provides responseDecodable. Change your function to

    func executeGet( completion: @escaping (Result<Welcome,Error>) -> Void) {
                AF.request("https://myURL",
                           method:.get,
                           headers:headers).responseDecodable(of: Welcome.self) { response in
    
            switch response.result {
                case .success(let welcome): 
                    completion(.success(welcome))
    
                case .failure(let error): 
                    completion(.failure(error))
            }
        }
    
    }
    

    which can even be simplified to

    func executeGet( completion: @escaping (Result<Welcome,Error>) -> Void) {
                AF.request("https://myURL",
                           method:.get,
                           headers:headers).responseDecodable(of: Welcome.self) { response in
    
            completion(response.result)
        }
    
    }
    

    And call it

    executeGet() { result in
        switch result {
            case .success(let welcome): print(welcome)
            case .failure(let error): print(error)
        }
    }
    

    If you get an error the model doesn't match the JSON and the error tells you exactly what's wrong