0

I am getting a response from the remote server from which i had created a jsonObject. now when I'm iterating the response for the key "instances" which contains a value of dictionary the response is shuffled. the key element of the dictionary which is a date is not coming in sequence while printing.

here is the json response:

{
    "data": [
        {
            "id": "apkqY19RthbN",
            "theater_id": "clzEVgztkcWB",
            "theater_audi_id": "7LFeNCaCrm8h",
            "movie_lang_id": "0VTEIjLeDxK1",
            "booking_start_date": null,
            "instances": {
                "2018-04-20": [
                    {
                        "id": "WRK81ahlWcZZ",
                        "show_time": "17:00:00"
                    },
                    {
                        "id": "cDqZjYKHP2xt",
                        "show_time": "20:00:00"
                    }
                ],
                "2018-04-21": [
                    {
                        "id": "DcVQhohv9C3W",
                        "show_time": "17:00:00"
                    },
                    {
                        "id": "bZOUk3AT6TMM",
                        "show_time": "20:00:00"
                    }
                ],
                "2018-04-22": [
                    {
                        "id": "5YJAydTua6b2",
                        "show_time": "17:00:00"
                    },
                    {
                        "id": "qKGXgWvV0r38",
                        "show_time": "20:00:00"
                    }
                ],
                "2018-04-23": [
                    {
                        "id": "AGciEXINppMe",
                        "show_time": "17:00:00"
                    },
                    {
                        "id": "YbKFBJV67hFW",
                        "show_time": "20:00:00"
                    }
                ],
                "2018-04-24": [
                    {
                        "id": "5vj9t1i5B4J3",
                        "show_time": "17:00:00"
                    },
                    {
                        "id": "um7UeNPJuAcv",
                        "show_time": "20:00:00"
                    }
                ],
                "2018-04-25": [
                    {
                        "id": "AbS69J4SM4gG",
                        "show_time": "17:00:00"
                    },
                    {
                        "id": "gKax9RXMLozN",
                        "show_time": "20:00:00"
                    }
                ]
            }
        }
    ]
}

here is my code that i had tried:

 _ = URLSession.shared.dataTask(with: request) { (Data, response, error) in
            if Data != nil{
                do{

                    let access = try JSONSerialization.jsonObject(with: Data!, options: []) as! [String: Any]
                   // print(access)
                    if let data = access["data"] as? [[String:Any]]{
                        if let id = data[0]["id"] as? String{
                            global.showID = id
                          //  print(data)
                        }
                        var j = 0
                        for l in 0..<data.count{
                        if let instances = data[l]["instances"] as? [String:Any] {
                            print(instances)
                            for (key,obj) in instances{
                                //for (key,obj) in (instances as [String:Any]){
                                //print(key)
                                var showDetails: [ShowData] = [ShowData]()
                                if let timeObj = obj as? [[String: Any]]{
                                    var k = 0
                                    for i in 0..<timeObj.count{
                                        let showTime = timeObj[i]["show_time"]
                                        //print(showTime!)

                                        let showID = timeObj[i]["id"]
                                        //print(showID!)

                                        let a = ShowData.init(showId: showID as! String, showtime: showTime as! String)
                                        showDetails.insert(a, at: k)
                                        //print(showDetails)
                                        k += 1
                                    }
                                }
                                let a = Shows(date: key , instance: showDetails)
                                self.showsDate?.insert(a, at: j)
                                //print(self.showsDate!)
                                j += 1
                            }
                            Completion(self.showsDate!)
                        }
                        }
                    }
                }catch let e{
                    print(e)
                }
            }
            }.resume()

print(instances) when i am printing this the output is :

["2018-04-25": <__NSArrayI 0x143bd1ac0>(
{
    id = AbS69J4SM4gG;
    "show_time" = "17:00:00";
},
{
    id = gKax9RXMLozN;
    "show_time" = "20:00:00";
}
)
, "2018-04-24": <__NSArrayI 0x143b3aca0>(
{
    id = 5vj9t1i5B4J3;
    "show_time" = "17:00:00";
},
{
    id = um7UeNPJuAcv;
    "show_time" = "20:00:00";
}
)
, "2018-04-23": <__NSArrayI 0x143be0ce0>(
{
    id = AGciEXINppMe;
    "show_time" = "17:00:00";
},
{
    id = YbKFBJV67hFW;
    "show_time" = "20:00:00";
}
)
, "2018-04-21": <__NSArrayI 0x143b349c0>(
{
    id = DcVQhohv9C3W;
    "show_time" = "17:00:00";
},
{
    id = bZOUk3AT6TMM;
    "show_time" = "20:00:00";
}
)
, "2018-04-22": <__NSArrayI 0x143b24290>(
{
    id = 5YJAydTua6b2;
    "show_time" = "17:00:00";
},
{
    id = qKGXgWvV0r38;
    "show_time" = "20:00:00";
}
)
, "2018-04-20": <__NSArrayI 0x143bd5430>(
{
    id = WRK81ahlWcZZ;
    "show_time" = "17:00:00";
},
{
    id = cDqZjYKHP2xt;
    "show_time" = "20:00:00";
}
)
]

the dates are not in sequence like in the response.

rish
  • 5
  • 3
  • Dictionary's don't guarantee key order. You might consider creating your own [`OrderedDictionary`](http://www.timekl.com/blog/2014/06/02/learning-swift-ordered-dictionaries/) – MadProgrammer Apr 21 '18 at 05:31
  • what should be the approach then to iterate the response in order. – rish Apr 21 '18 at 05:33
  • Well, it depends. As I updated, you could look into finding an implementation of an `OrderedDictionary` or you could keep the keys in a `Array`, which would act as the "ordering" mechanism, then you could just extract the value from the `Dictionary` through the `Array` – MadProgrammer Apr 21 '18 at 05:34
  • And this is dangerously close to a duplicate of [Swift - Stored values order is completely changed in Dictionary](https://stackoverflow.com/questions/29601394/swift-stored-values-order-is-completely-changed-in-dictionary?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa) – MadProgrammer Apr 21 '18 at 05:35
  • Besides, from the looks of it, they dates are in date order anyway, so, you could make a copy of the `keys` (into an `Array`), so the `Array` in the direction you want and use that as a vector back into the dictionary - `timeObj[sortedKeys[0]]` or something simular – MadProgrammer Apr 21 '18 at 05:37
  • ok i am looking for the implementation of an ordered dictionary. – rish Apr 21 '18 at 05:38
  • You could also have a look at [`Dictionary#sorted(by:)`](https://developer.apple.com/documentation/swift/dictionary/2298238-sorted) - which is basically the same idea - I would, however, suggest that the JSON data is badly formatted, but that's just my opinion – MadProgrammer Apr 21 '18 at 05:38

2 Answers2

1

Dictionary's don't maintain key order.

Let's just start with some basic data...

struct ShowData: CustomStringConvertible {
    var id: String
    var time: String
    
    var description: String {
        return "id = \(id); time = \(time)"
    }
}

var rawData: [String: [ShowData]] = [:]

rawData["2018-04-20"] = [
    ShowData(id: "WRK81ahlWcZZ", time: "17:00:00"),
    ShowData(id: "cDqZjYKHP2xt", time: "20:00:00")]
rawData["2018-04-21"] = [
    ShowData(id: "DcVQhohv9C3W", time: "17:00:00"),
    ShowData(id: "bZOUk3AT6TMM", time: "20:00:00")]
rawData["2018-04-22"] = [
    ShowData(id: "5YJAydTua6b2", time: "17:00:00"),
    ShowData(id: "qKGXgWvV0r38", time: "20:00:00")]
rawData["2018-04-23"] = [
    ShowData(id: "AGciEXINppMe", time: "17:00:00"),
    ShowData(id: "YbKFBJV67hFW", time: "20:00:00")]
rawData["2018-04-24"] = [
    ShowData(id: "5vj9t1i5B4J3", time: "17:00:00"),
    ShowData(id: "um7UeNPJuAcv", time: "20:00:00")]
rawData["2018-04-25"] = [
    ShowData(id: "AbS69J4SM4gG", time: "17:00:00"),
    ShowData(id: "AbS69J4SM4gG", time: "20:00:00")]

for key in rawData.keys {
    print(key)
}

For me, this prints out...

2018-04-25
2018-04-24
2018-04-23
2018-04-21
2018-04-20
2018-04-22

Now, you could look to finding a OrderedDictionary or you could Map the keys to an array and sort the array, which would give you vector back into the Dictionary

Or, you could sort the Entry data of the Dictionary...

let sorted = rawData.sorted { (lhs, rhs) -> Bool in
    return lhs.key < rhs.key
}

for entry in sorted {
    print(entry.key)
}

Which outputs...

2018-04-20
2018-04-21
2018-04-22
2018-04-23
2018-04-24
2018-04-25

Now, what's important to remember here is, this is nothing more then a String comparison, but you're dealing with dates. Personally, I hate dealing with String date/time values and as soon as I can, I convert to something which is actually practical for there purpose...

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-mm-dd"

let actuallySortedByDate = rawData.sorted { (lhs, rhs) -> Bool in
    return formatter.date(from: lhs.key)! < formatter.date(from: rhs.key)!
}

for entry in sorted {
    print(entry.key)
}

Which outputs...

2018-04-20
2018-04-21
2018-04-22
2018-04-23
2018-04-24
2018-04-25

While this might not look any different from the first sorting, the comparisons performed are more actual to the data type (date objects).

Important

This is a demonstration of an idea. It's very important to understand that it's possible for the key to be invalid and the Date conversion to fail. Personally, I'd prefer to have the rawData keyed by Date, rather the String as it allows for an earlier detection of the issue. I've used a "forced unwrap" in the example purely for demonstration - you should validate the keys before hand and ensure that data is correct

Caveat: Mapping the keys in a Dictionary is actually a lot harder then it would seem to need to be. You can have a look at What's the cleanest way of applying map() to a dictionary in Swift? for more details. Needs less to say, use Swift 4!

Okay, so what this attempts to do, is convert the original [String: ShowData] Dictionary to a [Date: ShowData] Dictionary in a way which would allow you to deal with the possibility of invalid String keys.

enum ParserError: Error {
    case invalidDateFormat
}

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-mm-dd"
do {
    let mapped = try Dictionary(uniqueKeysWithValues: rawData.map({ (entry) throws -> (Date, [ShowData]) in
        guard let date = formatter.date(from: entry.key) else {
            throw ParserError.invalidDateFormat
        }
        return (Calendar(identifier: .gregorian).startOfDay(for: date), entry.value)
    }))

    let actuallySortedByDate = mapped.sorted { (lhs, rhs) -> Bool in
        return lhs.key < rhs.key
    }
    
    for entry in actuallySortedByDate {
        print(entry.key)
    }
} catch let error {
    print(error)
}

And this eventually ends up printing:

2018-01-19 13:00:00 +0000
2018-01-20 13:00:00 +0000
2018-01-21 13:00:00 +0000
2018-01-22 13:00:00 +0000
2018-01-23 13:00:00 +0000
2018-01-24 13:00:00 +0000

ps: The idea is to try and take the time component out of the equation

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

Dictionaries are unordered. You will also find that dealing with variable keys (your dates) can be tricky too. You should make instances an array and put the date in a dictionary value.

If you do this then you can make a couple of structs that adopt Codable and reduce your JSON parsing down to 1 line

Paulw11
  • 108,386
  • 14
  • 159
  • 186