16

I have the following situation:

NSDictionary *params = @{
    @"Checkout" : @{
        @"conditions" : @{@"Checkout.user_id" : @1},
        @"order" : @{@"Checkout.id" : @"DESC"}
    },
    @"PaymentState" : @[],
    @"Offer" : @[]
};

This dictionary contains params for a webservice request passing a JSON string with the webservice URL. I get the JSON string using NSJSONSerialization class, like this:

NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

The problem is: jsonString "keys" is ordered differently from the original params dictionary keys order, like this:

{
"Offer":[],
"PaymentState":[],
"Checkout":{
    "conditions":{"Checkout.user_id":6},
    "order":{"Checkout.id":"DESC"}
}

}

That is, the "PaymentState" and "Offer" keys come first in jsonString, and i need maintain the original order. This is very important, like this:

{
"Checkout":{
    "conditions":{"Checkout.user_id":6},
    "order":{"Checkout.id":"DESC"}
},
"Offer":[],
"PaymentState":[]

}

So guys, how can i do that??

vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
Rodrigo Salles
  • 257
  • 1
  • 2
  • 9
  • 1
    Just curious... Why do you need the same order? – mattyohe Feb 28 '13 at 01:00
  • NSDictionary keys do not have an order. And the entries in a JSON "object" do not have an order either. You shouldn't need to have either conform to some order. (Why do you say it's "very important"???) – Hot Licks Feb 28 '13 at 01:09
  • Because I'm requesting data from the "Checkout" API and Checkout model needs to be the first key, while the others, Offer and PaymentState, are the related models. – Rodrigo Salles Feb 28 '13 at 01:12
  • If the API is expecting "JSON" to be ordered like that it's not JSON. Have you tried with the keys in a different order?? – Hot Licks Feb 28 '13 at 01:15
  • Unfortunately that is the architecture of the API, and i can't change this. And the JSON is converted to a PHP array in the API. So, the order is important. – Rodrigo Salles Feb 28 '13 at 01:15
  • You're sure. It says they must be in that order, not simply that it presents an example of them in that order?? (And have you talked to the API owners?) – Hot Licks Feb 28 '13 at 01:16
  • 2
    @mattyohe - Sometimes a collection that has a predictable/consistent iteration order is useful. In Java, for instance, there are the built-in `LinkedHashMap` and `LinkedHashSet` classes which implement `Map` and `Set` semantics while guaranteeing that iteration will always follow insertion order. They're not the right data structure(s) to use all the time, but they can be useful in some cases. – aroth Feb 28 '13 at 01:16
  • 4
    @aroth Alright that's fine that those classes exist in Java, but the question above is specific to JSON which as Me1000's answer below informs us, JSON is inherently unordered. Basically someone's bad API is expecting "JSON" and the OP has to deal with it. Not pretty. – mattyohe Feb 28 '13 at 01:23
  • When the JSON parameter is handled by the API, it is converted to a PHP array, by json_decode() function. That function maintain the order of the JSON string. – Rodrigo Salles Feb 28 '13 at 01:28
  • 1
    @mattyohe - I think the question is more about how you accomplish that task (predictable iteration of a map/dictionary) in iOS/Objective-C. The fact that from a pedantic point of view JSON is un-ordered is irrelevant. It just means that the API that the OP has to work with isn't technically accepting JSON. It's accepting a JSON-like string. Which the OP still has to generate, using the tools available to him in the iOS SDK. – aroth Feb 28 '13 at 01:32
  • 1
    That doesn't make sense, even in PHP. $foo = Array(); $foo["bar"] = "my value"; echo $foo[0]; Doesn't work, so how does order make any difference when you're looking it up via a key? – Me1000 Feb 28 '13 at 01:36
  • It's what @aroth said. Doesn't matter what it is... I have to do that with the tools I have. – Rodrigo Salles Feb 28 '13 at 01:41
  • @Me1000 - I get your point. And I agree with you, and I agree with all the other guys. But that is not what I need, answers about the architecture of the API or how JSON is. I need to do what I asked for. – Rodrigo Salles Feb 28 '13 at 01:53
  • 2
    If you need this incredibly specific (and non-JSON) behavior... why don't you just write it yourself? How hard can it be to spit out the "JSON" string with the order you want. – Francisco Ryan Tolmasky I Feb 28 '13 at 01:59
  • @FranciscoRyanTolmaskyI - You mean a NSString object containing a JSON (or JSON-like) string? – Rodrigo Salles Feb 28 '13 at 02:05
  • If thats what you want – Francisco Ryan Tolmasky I Feb 28 '13 at 02:07
  • 8
    Another reason for having a defined order of elements is, that it is easier to spot differences, when you have a JSON file under version control. This is more readable for a human and has nothing to do with any implementation details. It's just very convenient. – Klaas Aug 10 '13 at 23:48
  • 6
    furthermore, if you serialize the json to NSData and you use an MD5 string to determine whether the data has changed, it's good to know that it hasn't... – horseshoe7 Jan 07 '14 at 18:02

3 Answers3

10

I use OrderedDictionary from CocoaWithLove whenever I need to keep the order of my dictionary keys.

Basically, OrderedDictionary contains an NSMutableDictionary and an NSMutableArray for the keys inside it, to keep track of the order of the keys. It implements the required methods for subclassing NSDictionary/NSMutableDictionary and just "passes" the method call to the internal NSMutableDictionary.

neilvillareal
  • 3,935
  • 1
  • 26
  • 27
  • 2
    When do you "need" to keep the order of dictionary keys? – mattyohe Feb 28 '13 at 01:09
  • At one point, the web service that I needed to connect to had to have the same order of the parameters. (Weird, I know. I use a dictionary and form the query string from the contents of the dictionary.) Also, sometimes I use a dictionary to keep the contents of a table view (but that may not be good practice). – neilvillareal Feb 28 '13 at 01:12
  • 3
    @mattyohe - Whenever it's useful to have a dictionary that is also a list. A "dictionary" (as in, one of those things that list words in alphabetical order along with the definition for each word) is a perfect example. It's a mapping of keys to values, but the keys have a natural ordering to them. And it's sometimes convenient to have a single data structure that can accommodate that. – aroth Feb 28 '13 at 01:20
  • 2
    Another option appears to be this: https://github.com/Marxon13/NSOrderedDictionary – aroth Feb 28 '13 at 01:21
  • 2
    @neilvillareal - OrderedDictionary worked very well, exactly what I needed. Thanks!! – Rodrigo Salles Feb 28 '13 at 11:25
  • For Ordered dictionaries there are other SO links. This one is about JSON and NSJSONSerialization, which cannot be configured to use any custom ordered dictionary class. – LolaRun Nov 09 '16 at 09:47
10

According to the JSON spec a JSON object is specifically unordered. Every JSON library is going to take this into account. So even when you get around this issue for now, you're almost certainly going to run into issues later; because you're making an assumption that doesn't hold true (that the keys are ordered).

Me1000
  • 1,760
  • 1
  • 12
  • 14
  • 1
    I agree, but unfortunately that is the architecture of the API, and i can't change this. So I have to find a way to do that. – Rodrigo Salles Feb 28 '13 at 01:35
  • 7
    That fact that what the OP needs to generate is not technically JSON is largely irrelevant. His task is to generate a JSON-like string with a particular field order. Telling him that "it's not JSON" doesn't help him do that. – aroth Feb 28 '13 at 01:36
  • 1
    I'm sorry, but providing an answer to this specific question doesn't help when the OP is relying on an implementation detail in PHP that could change. Recommending bad software practices is going to end up doing more harm than good. – Me1000 Feb 28 '13 at 01:50
  • 1
    @Me1000 - Then tell that to the person who wrote the API that expects the JSON-like thing in the first place. It's not the OP's fault that he has to integrate with it, nor is it within his power to modify it, as far as I can tell. The underpinning question of how to get an ordered dictionary in iOS is entirely valid. – aroth Feb 28 '13 at 01:54
  • 2
    Since I don't know who wrote the API (although, apparently the OP has access to the code, since he knows underlying implementation details) that's a bit difficult… so my advice is quite simple… yell at the person who wrote the terrible API, or use a better checkout provider. I certainly wouldn't trust them. – Me1000 Feb 28 '13 at 01:58
  • @aroth - neilvillareal's answer below has solved my question. Thanks!! – Rodrigo Salles Feb 28 '13 at 11:28
  • For a sorted dictionary there are other SO links. But i'll side with @Me1000 on this one. The question is about JSON and the nsjsonserializer. There's no way to make the serializer use another dictionary type, unless you make a custom one. Additionally this spec tip is very important. It means that whenever we need ordered things they must come in array rather than dictionary. And that change should start from the APIs level before reaching our end. – LolaRun Nov 09 '16 at 09:45
8

While NSDictionary and Dictionary do not maintain any specific order for their keys, starting on iOS 11 and macOS 10.13, JSONSerialization supports sorting the keys alphabetically (see Apple documentation) by specifying the sortedKeys option.

Example:

let data: [String: Any] = [
    "hello": "world",
    "a": 1,
    "b": 2
]

let output = try JSONSerialization.data(withJSONObject: data, options: [.prettyPrinted, .sortedKeys])
let string = String(data: output, encoding: .utf8)

// {
//  "a" : 1,
//  "b" : 2,
//  "hello" : "world"
// }
Eneko Alonso
  • 18,884
  • 9
  • 62
  • 84
  • 2
    This is pretty useful for readability, but it would be nice if you could pass in your own sorting function for custom sorts as an option...sometimes straight alphabetical isn't what you want. Or optionally, pass in the keys in an array (that you've already sorted) to specify sort order. – Ben Stahl Aug 20 '19 at 17:31