Search code examples
swiftnsarraynsdictionaryswift4

How to sort JSON Data in Array in swift 4


I have JSON array like this

 var json = NSArray()  // array with json objects

 //print json >>
   json = (
                {
                Name = "Alen";
                Score = 500;
            },
                {
                Name = "John";
                Score = 0;
            },
                {
                Name = "Mark";
                Score = 2000;
            },
                {
                Name = "Steve";
                Score = 300;
            },
                {
                Name = "Ricky";
                Score = 900;
            }
        )

and i can access its objects as

(json[0] as! NSDictionary).object(forKey: "Name")
(json[0] as! NSDictionary).object(forKey: "Score")

I want to sort this JSON array according to scores.

I found the answers like

let sortedArray = json.sorted(by: { $0.0 < $1.0 })

which gives error

Value of type 'Any' has no member '0'

Then I tried this

 let sortedArray = (json as! NSDictionary).sorted {(aDic, bDic)  -> Bool in
                return aDic.key < bDic.key
            }

It gave error

Binary operator '<' cannot be applied to two 'Any' operands

Can you please guide me to sort the array according to score in swift 4?


Solution

  • That's a very good example why you are strongly discouraged from using NSArray and NSDictionary in Swift.

    Both collection types don't provide type information so everything is treated as Any. Most of the shared generic API of the Swift Standard library cannot be used with Any so you are not able to take advantage of the powerful generic functions unless you add a lot of ugly type casts.

    If all values are String declare your array as

    var json = [[String:String]]()
    

    Then you can sort the array with

    let sortedArray = json.sorted { $0["Score"]! < $1["Score"]! }
    

    The most recommended solution is to decode the JSON directly into a custom struct

    struct Player : Decodable {
        let name : String
        let score : String
    
        private enum CodingKeys : String, CodingKey { case name = "Name", score = "Score" }
    }
    

    Then you get rid of all type casting and you can sort by the property name

    var players = [Player]()
    
    let jsonString = """
    [{"Name" : "Alen", "Score" : "500"},
    {"Name" : "John", "Score" : "0"},
    {"Name" : "Mark", "Score" : "2000"},
    {"Name" : "Steve", "Score" : "300"},
    {"Name" : "Ricky", "Score" : "900"}]
    """
    
    let data = Data(jsonString.utf8)
    do {
        players = try JSONDecoder().decode([Player].self, from: data)
        let sortedPlayers = players.sorted{ $0.score.compare($1.score, options: .numeric) == .orderedAscending }
        print(sortedPlayers)
    } catch { print(error) }
    

    Edit:

    To load the JSON use an asynchronous way (URLSession)

    Never load data from a remote URL with synchronous Data(contentsOf.

    var players = [Player]()
    
    let jsonUrl = URL(string: "url.json")! 
    let task = URLSession.shared.dataTask(with : url) { [unowned self] (data, _, error) in 
        if let error = error { print(error); return }
        do {
            players = try JSONDecoder().decode([Player].self, from: data!).sorted{ $0.score < $1.score }
            DispatchQueue.main.async { // reload the table view if necessary
                self.tableView.reloadData()
            }
        } catch { print(error) }
    }
    task.resume()