How to interpret this downloaded array.

Hi, I'm trying to use ADF instead of SO.


I have the following code:


for i in 0..           
            currentStation = [trainArray[i]]
            
           print("currentStation: ", currentStation[0] )
            
            print ("currentStation type", type(of: currentStation))
           
        }


This results in the following console output:


currentStation:  [AnyHashable("Car"): AnyHashable("8"), AnyHashable("Group"): AnyHashable("2"), AnyHashable("DestinationCode"): AnyHashable("F11"), AnyHashable("Min"): AnyHashable("BRD"), AnyHashable("Destination"): AnyHashable("Brnch Av"), AnyHashable("Line"): AnyHashable("GR"), AnyHashable("DestinationName"): AnyHashable("Branch Ave"), AnyHashable("LocationName"): AnyHashable("Archives-Navy Memorial-Penn Quarter"), AnyHashable("LocationCode"): AnyHashable("F02")]
currentStation type Array

I'm confused how use the values within currentStation? Any help will be appreciated.

Accepted Reply

In objC, I'd use value forKey...

Seems you are infected by some bad examples. You should use `objectForKey:` in Objective-C, unless you are trying to implement some KVC-compliant feature. Or else, you can use subscript (`[...]`), which implicitly calls `objectForKeyedSubscript:` in modern Objective-C.


And you always use subscript to access a value for a specific key of Swift Dictionary.

(I do strongly recommend you to learn very basic parts of Swift before writing actual code. It seems you are very near trying to learn English without learning how to read/write alphabets.)


        for train in self.trainArray {
            let car = train["Car"] as? String ?? "*car unspecified*"
            let group = train["Group"] as? String ?? "*group unspecified"
            let destinationCode = train["DestinationCode"] as? String ?? "*destinationCode unspecified*"
            //...
            print(car, group, destinationCode/*, ... */)
        }


But, as I wrote before, you should better try using Codable, unless you have some reason to avoid it.


Preparing two Codable types:

struct JsonResult: Codable {
    var trains: [Train]
    
    private enum CodingKeys: String, CodingKey {
        case trains = "Trains"
    }
}
struct Train: Codable {
    var car: String
    var group: String
    var destinationCode: String
    var min: String
    var destination: String
    var line: String
    var destinationName: String
    var locationName: String
    var locationCode: String

    private enum CodingKeys: String, CodingKey {
        case car = "Car"
        case group = "Group"
        case destinationCode = "DestinationCode"
        case min = "Min"
        case destination = "Destination"
        case line = "Line"
        case destinationName = "DestinationName"
        case locationName = "LocationName"
        case locationCode = "LocationCode"
    }
}

(You may need to fix some types of proerties, for example, `String?` instead of `String`.)


And you can use them as:

    var trainArray: [Train] = []
    
    func processResponse(using data: Data) {
        do {
            let jsonResult = try JSONDecoder().decode(JsonResult.self, from: data)
            guard !jsonResult.trains.isEmpty else {
                print("Result is not a valid JSON object")
                //You may need to handle error
                return
            }
            trainArray = jsonResult.trains
            buildDetailsForMap()
            configureTable()
        } catch {
            print("Could not parse loaded json with error:\(error)")
            //You may need to handle error
        }
    }

Maybe it is easier for you to access the proerties of `Train` than accessing Dictionary of Swift.

Replies

The line 01. of your code is broken and you should better explain how you get `trainArray`.


And in line 02., you are creating a single-element Array, so the output 02. is obvious.


The output 01. represents `currentStation[0]` (not `currentStation`) is a Dictionary whose key is `AnyHashable` (often found in returned Dictionary from Objective-C code). And this is not so common, the value type of the Dictionary is also `AnyHashable`.


I'm confused how use the values within currentStation? Any help will be appreciated.


This is the value you get. Please explain how you want to use it. (And for my interest, how you get this value.)

@OOPer, Thanks for the reply. Let me see if I can answer your questions.


1. Line 1 should read:

for i in 0..<trainarray.count


2. trainArray is derived from a web service. Sorry for the large code snip but this is how the array is populated:



@IBAction func updateTrains() {
    // Set up the URL request
    let tempString1: String =  "************"
    let tempString2: String = "*************"
    
    pathString = tempString1 + codeString + tempString2
  
    
    guard let url = URL(string: pathString) else {
    print("Error: cannot create URL")
    return
    }
    
    
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        if error != nil {
            print(error!.localizedDescription)
        }
        
        if let httpResponse = response as? HTTPURLResponse {
            print("statusCode: \(httpResponse.statusCode)")
            if httpResponse.statusCode != 200 {
                self.showError()
                return
                
            }
        }
       
        guard let usableData = data else {
            self.showError()
            return
        }
        
     
        do {
            
            //Get back to the main queue
            DispatchQueue.main.async {
                self.processResponse(using: usableData)
            }
            
        } //End implementing URLSession
    }
    task.resume()
    
}


    func processResponse(using data: Data?) {
       
        let error: Error? = nil
        var jsonResult: [AnyHashable : Any] = [:]
        
        if nil != data {
            do {
                if let data = data,  let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [AnyHashable : Any] {
                    jsonResult = json
                }
            } catch {
            }
        }
        
        if error != nil || jsonResult.count == 0 {
            if let error = error {
                print("Could not parse loaded json with error:\(error)")
            }
            
        } else {
            DispatchQueue.main.async(execute: {
                if let value = jsonResult["Trains"] as? [AnyHashable] {
                    self.trainArray = value
                }
                
                self.buildDetailsForMap()
                
            })
        }
        configureTable()
    }

and trainArray is defined as: var trainArray: [Any] = []


I plan to extract the station names and minutes as rows in a WKInterfaceTable.. The other info will be used in follow on controllers. I've logged the urlRequest, and the results are fine.


Thanks for any help.


David

Thanks for showing your code. And please do not hesitate to show some small (less than 100 lines!) fragment of codes which will explain what's happening clearly.


The most important part which makes such confusing result is here:

if let value = jsonResult["Trains"] as? [AnyHashable] {

Your `trainArray` is declared as `[Any]`, casting the json result `as? [AnyHashable]` seems to be a mistake.


And in JSON, all keys must be String, so you have no need to use `AnyHashable`.


The value for "Trains" seems to be an Array of Dictionary, so you should better use more appropriate data type for `trainArray`.


I would re-write your `processResponse` as:

    var trainArray: [[String: Any]] = []
    
    func processResponse(using data: Data) {
        do {
            guard let json = try JSONSerialization.jsonObject(with: data) as? [String : Any],
                !json.isEmpty else {
                print("Result is not a valid JSON object")
                //You may need to handle error
                return
            }
            guard let value = json["Trains"] as? [[String: Any]] else {
                print("Result lacks valid Trains info")
                //You may need to handle error
                return
            }
            self.trainArray = value
            self.buildDetailsForMap()
            configureTable()
        } catch {
            print("Could not parse loaded json with error:\(error)")
            //You may need to handle error
        }
    }

Some points:

- You have `guard` before calling `processResponse`, so your `usableData` can never be nil. The parameter type should be non-Optional,

- You have `DispatchQueue.main.async {...}` when calling `processResponse`. It is quite suspicious you need another `DispatchQueue.main.async {...}`.

- You declare `error`, but it is never assigned.

- You ignore many illegal cases silently, consider what should happen on `else` cases, when you write checking `if`s. And never leave `catch` block empty.

- `.mutableContainers` has no meaning in Swift. Remove it.


But I recommend to use `Codable` when possible. And I guess your JSON response is suitable for Codable.

Thanks for all the tips !


I have changed the definition of stationArray to:


var trainArray: [[String: Any]] = []


After replacing the `processResponse` code, I now get this console output:


configureTable() trainArray:  [["LocationCode": G01, "Min": , "LocationName": Benning Road, "Car": 6, "DestinationCode": , "Line": BL, "Destination": Nat''l Air, "DestinationName": Nat''l Air, "Group": 2]]
currentStation count:  1
currentStation type Array

I still am confused how to access the elements within "currentStation", eg the value for "LocationName", "Car", etc.


In objC, I'd use value forKey...


How can I do this in Swift? I really appreciate your help.

In objC, I'd use value forKey...

Seems you are infected by some bad examples. You should use `objectForKey:` in Objective-C, unless you are trying to implement some KVC-compliant feature. Or else, you can use subscript (`[...]`), which implicitly calls `objectForKeyedSubscript:` in modern Objective-C.


And you always use subscript to access a value for a specific key of Swift Dictionary.

(I do strongly recommend you to learn very basic parts of Swift before writing actual code. It seems you are very near trying to learn English without learning how to read/write alphabets.)


        for train in self.trainArray {
            let car = train["Car"] as? String ?? "*car unspecified*"
            let group = train["Group"] as? String ?? "*group unspecified"
            let destinationCode = train["DestinationCode"] as? String ?? "*destinationCode unspecified*"
            //...
            print(car, group, destinationCode/*, ... */)
        }


But, as I wrote before, you should better try using Codable, unless you have some reason to avoid it.


Preparing two Codable types:

struct JsonResult: Codable {
    var trains: [Train]
    
    private enum CodingKeys: String, CodingKey {
        case trains = "Trains"
    }
}
struct Train: Codable {
    var car: String
    var group: String
    var destinationCode: String
    var min: String
    var destination: String
    var line: String
    var destinationName: String
    var locationName: String
    var locationCode: String

    private enum CodingKeys: String, CodingKey {
        case car = "Car"
        case group = "Group"
        case destinationCode = "DestinationCode"
        case min = "Min"
        case destination = "Destination"
        case line = "Line"
        case destinationName = "DestinationName"
        case locationName = "LocationName"
        case locationCode = "LocationCode"
    }
}

(You may need to fix some types of proerties, for example, `String?` instead of `String`.)


And you can use them as:

    var trainArray: [Train] = []
    
    func processResponse(using data: Data) {
        do {
            let jsonResult = try JSONDecoder().decode(JsonResult.self, from: data)
            guard !jsonResult.trains.isEmpty else {
                print("Result is not a valid JSON object")
                //You may need to handle error
                return
            }
            trainArray = jsonResult.trains
            buildDetailsForMap()
            configureTable()
        } catch {
            print("Could not parse loaded json with error:\(error)")
            //You may need to handle error
        }
    }

Maybe it is easier for you to access the proerties of `Train` than accessing Dictionary of Swift.

Thanks a ton - the light came on.