Looping through an dictionary; why do I not get the keys alphabetically? - iphone

I am writing an iPhone app using S7Graphview and I have saved some dates and results to a .plist as keys and values in a dictionary, both as strings. My plist looks like this:
<dict>
<key>2011-05-11</key>
<string>23</string>
<key>2011-05-12</key>
<string>23</string>
<key>2011-05-13</key>
<string>23</string>
<key>2011-05-14</key>
<string>43</string>
<key>2011-06-14</key>
<string>43</string>
</dict>
Then I use this loop to load those values into the graphview:
NSMutableDictionary* dictionary = [[NSMutableDictionary alloc]init];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
int i = 1;
if ([dictionary count] > 0) {
for (NSString* key in dictionary){
NSString* verdiString = [dictionary objectForKey:key];
CGFloat verdi = [verdiString intValue];
NSDate *dato = [[NSDate alloc]init];
NSLog(#"key=%# value=%#", key, [dictionary objectForKey:key]);
dato = [self stringTilDato:key];
[items.list_ addObject:[[GraphInfo alloc] initWithID:i name:verdiString value:verdi date:dato]];
i++;
}
}
The "stringTilDato" method converts the date string to a NSDate. The values get loaded into the items.list, but in the wrong order! The NSLog reports:
key=2011-05-14 value=43
key=2011-05-13 value=23
key=2011-05-12 value=23
key=2011-06-14 value=43
key=2011-05-11 value=23
key=2011-05-14 value=43
key=2011-05-13 value=23
key=2011-05-12 value=23
key=2011-06-14 value=43
key=2011-05-11 value=23
(Don't know why it goes through the keys twice, btw, but I dont't believe that's important). I thought the keys would be read alphabetically, or at least in the order of the plist. Why does the plist get loaded into the dictionary in this order, or is it my loading loop that is the problem?
Hope there is someone out there who can help me :)

A dictionary is not an ordered data structure. You can assume no particular order of the keys, and adding or removing a key can change the order completely. If you need an ordered data structure then use an array, or after getting all the keys sort them.

The documentation for NSDictionary says specifically:
The order of the elements in the array
is not defined.
However, if you want the keys sorted in a particular order, there are at least three methods that return a sorted array of keys. Look at the NSDictionary reference page for methods starting with "keysSortedBy..."

Dictionaries do not ensure an ordered key output. If you want the keys displayed in a particular order, you will have to sort them prior to accessing their values and printing the result.

Related

Present JSON string in sorted order from NSDictionary

I made one JSON string from the NSDictionary. The JSON string which I created doesn't come present the items in the order I entered keys and value pair in the NSDictionary .
Now I need all keys and value pairs returned as JSON string in alphabetic order. I tried lot of ways but not getting sorting of JSON string in alphabetic order.
Here's an example of the way the final string is being presented:
{
"SubscriberID" : "603",
"Amount" : "1",
"MerchantID" : "100012",
"Channel" : "Wallet",
"RequestCode" : "0331",
"PosID" : "0465F35F5577CUST",
"TID" : "0000014",
"Stan" : "NA"
}
How do I ensure the items are presented the way I entered them? Or how can I specify the items are alphabetically sorted?
SBJson is able to do this, set sortKeys to YES on SBJsonWriter
http://superloopy.io/json-framework/
SBJsonWriter *writer = [[SBJsonWriter alloc] init];
writer.sortKeys = YES;
// Turn on humanReadable if you also need it pretty-printed
// writer.humanReadable = YES;
NSData *result = [writer dataWithObject:myDictionary];
Like many key-value storage classes, NSDictionary does not guarantee order of the elements you've added. It is a mistake to assume that the key/value pairs, or keyset, will be returned with any particular order.
Further, JSON objects are unordered in the same fashion. You should not care about the order in which the objects are added. Nor should you (or your recipient) rely on being provided a JSON object with 'sorted' keys, as that's not really a valid concept with these particular structures. While ordering the keys might result in an iterator traversing the keys in the order you expect, it's not a guarantee, and should not be relied on.
I think you should revisit why you need the keys sorted in the first place, and see if you can find a way to avoid a dependency on their alphabetical ordering.
Edit: You mention the server requires an SHA hash of the JSON string. If you must, you can create a sorted JSON string by sorting the keys in an NSMutableArray, then creating the JSON string from those keys.
NSMutableArray *sortedKeys = [NSMutableArray arrayWithArray:[myDict allKeys]];
[sortedKeys sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
NSMutableString *jsonString = [[NSMutableString alloc] init];
[jsonString appendString:#"{"];
for (NSString *key in sortedKeys) {
[jsonString appendFormat:#"\"%#\"", key];
[jsonString appendString:#":"];
[jsonString appendFormat:#"\"%#\"", [myDict objectForKey:key]];
[jsonString appendString:#","];
}
if ([jsonString length] > 2) {
[jsonString deleteCharactersInRange:NSMakeRange([jsonString length] - 1, 1)];
}
[jsonString appendString:#"}"];
This code hasn't been tested, so you might need to play with it a bit. Thought it would be much better if you could find a JSON library to do this for you, although you might not have as much control over the key ordering.
You can't do it (or at least you can't rely on that). JSON objects are unordered sets as you can read on JSON.org.
Actually,the system method [NSString stringWithFormat:] is what you need.So here is the easy way:
NSDictionary* inputDictionary = #{
#"SubscriberID" : #"603",
#"Amount" : #"1",
#"MerchantID" : #"100012",
#"Channel" : #"Wallet",
#"RequestCode" : #"0331",
#"PosID" : #"0465F35F5577CUST",
#"TID" : #"0000014",
#"Stan" : #"NA"
};
NSString* sortedJsonStr = [NSString stringWithFormat:#"%#", inputDictionary];
NSLog(#"sorted json string is %#", sortedJsonStr);
The result is:
{
Amount = 1;
Channel = Wallet;
MerchantID = 100012;
PosID = 0465F35F5577CUST;
RequestCode = 0331;
Stan = NA;
SubscriberID = 603;
TID = 0000014;
}
The question does not make sense, and therefore cannot be answered in its current form.
If you encode some JSON as {"keyB":"valueB","keyA":"valueA"}, take it's checksum, and then transmit the encoded JSON and the checksum to the remote site, the remote site has little choice but to take the checksum of the JSON as received and compare that. To do the checksum on sorted values it would have to decode the received JSON string into an NSDictionary, re-encode into "sorted" JSON, and then take the checksum of the reconstituted JSON, and that would be a lot of extra effort for no reason.
Far more likely is that there is some difference in either the way the checksum is being computed (padding of the SHA256 input, eg) or some difference in the JSON strings being used -- code page differences, escaped characters, one end is doing it on Base64 and the other not, etc.

xcode iPhone array and dictionary [noob]

I'm sorry for this (probably very) noob question, but i've been asked about this and can't see what's wrong (i'm java tought..)
This is what I have, data is loaded via JSON:
NSDictionary *myvalues = [myres objectForKey:#"0"];
this is the content if I output via NSLog:
({id = "1a";myval = 5;},
{id = "2b";myval="24.6";})
how do I iterate through myvalues and how do I get the values id and myval? Something like this i'm getting stuck:
for (NSArray* myvals_array in myvalues)
First it looks like the returned value is an Array, the content inside of the parentheses() denotes this. So I would try and set it as such instead of a Dictionary. Then you can enumerate through the array of dictionary's and get each dictionary inside:
for (id object in myvalues) {
NSDictionary *currentObject = (NSDictionary*)object;
NSString *myID = [currentObject valueForKey:#"id"];
NSString *myValue = [currentObject valueForKey:#"myval"];
NSLog(#"ID:%# VALUE:%#",myID,myValue);
}
This will enumerate through the array and create a dictionary for each entry, then get the values for each of the two elements inside. I just NSLog() them here but you can do whatever you want with the values.

Update original NSMutableArray after filtering with NSPredicate

I have recently started programming for the iOS Platform but now I need some help figuring out how to do 'something':
For my application I fetch some JSON data and put this data as objects into an Array
This Array is written to my own PLIST file (in the docs directory)
Now when the users starts a sync action I:
Fetch the data from the PLIST
Get the timestamp for a certain object in the Array that came from the PLIST
Use timestamp in new JSON request (for the new data)
So far so good.
Now for my (current) problem -> After receiving the new data (JSON req) I wish to update the timestamp of this 'certain' object in the array (and write this to the Plist).
Using an NSPredicate I am able to find the right set of data within the main Array (stampArr).
NSString *documentsDir = [NSHomeDirectory()stringByAppendingPathComponent:#"Documents"];
NSString *plistPath = [documentsDir stringByAppendingPathComponent:#"stamps.plist"];
NSMutableArray *stampArr = [[NSMutableArray alloc] initWithContentsOfFile:plistPath];
NSPredicate *filter = [NSPredicate predicateWithFormat:#"eventid = 1"];
NSMutableArray *filteredStampArr = [stampArr filteredArrayUsingPredicate:filter];
But now, after I update the filteredStampArr, I want to update the main Array with the data from the filtered Array.
In other words, I need to update the object from the Array with the new 'timestamp' (field of object).
I could off course use something like [stampArr addObject: [filteredStampArr copy]] after changing the filterd array but that would just create a duplicate of the information. I wish to overwrite the original object.
Somehow (I think) I need a 'pointer' that tells me the location of the data in the original array so that I can change the data directly in the main array?
(I hope my questions is clear - If not please say so)
Get the item, find it's index in stampArr and replace it with the newItem.
NSArray *filteredStampArr = [stampArr filteredArrayUsingPredicate:filter];
id item = [filteredStampArr objectAtIndex:0]; // id because the type of the item is not known
NSUInteger itemIndex = [stampArr indexOfObject:item];
[stampArr replaceObjectAtIndex:itemIndex withObject:newItem];
When you get filteredArray, you can directly update objects in it (not replace) and thay willbe uopdated in main array.
Read the API carefully!
try:
[stampArr filterUsingPredicate:];

parsing JSON object and sub-elements on iphone app

I am building an app that uses a UPC data base API. I am getting back a JSON object, example from here: http://www.simpleupc.com/api/methods/FetchNutritionFactsByUPC.php
{
"success":true,
"usedExternal":false,
"result"
{
"calories_per_serving":"150",
"cholesterol_per_serving":"15",
"cholesterol_uom":"Mg",
"dvp_calcium":"30",
"dvp_cholesterol":"4",
"dvp_iron":"2",
"dvp_protein":"17",
"dvp_saturated_fat":"8",
"dvp_sodium":"10",
"dvp_total_fat":"4",
"dvp_vitamin_a":"10","
"dvp_vitamin_c":"0",
"dvp_vitamin_d":"25",
"fat_calories_per_serving":"25",
"fiber_per_serving":"<1",
"fiber_uom":"G",
"ingredients":"Fat Free Milk, Milk, Sugar, Cocoa (Processed With Alkali),
Salt, Carrageenan, Vanillin (Artificial Flavor),
Lactase Enzyme, Vitamin A Palmitate And Vitamin D3.",
"protein_per_serving":"8",
"protein_uom":"G",
"size":"240",
"units":"mL",
"servings_per_container":"8",
"sodium_per_serving":"230",
"sodium_uom":"Mg",
"total_fat_per_serving":"2.5",
"total_fat_uom":"G",
"trans_fat_per_serving":"0",
"trans_fat_uom":"G",
"upc":"041383096013"
}
}
My problem is with parsing the "ingredients" element, which is a sub list of the object dictionary.
How would you suggest parsing the ingredients list? If I could get it to an NSArray, assuming commas are separators, that would have been great.
I tried to do this, but looks like its just a string, so no way to parse it.
Any suggestion would be more than welcome. Thanks!
//Thats the whole JSON object
NSDictionary *json_dict = [theResponseString JSONValue];
//Getting "results" which has all the product info
NSArray *myArray = [[NSArray alloc] init];
myArray = [json_dict valueForKey:#"result"];
Now how do I get "ingredients" from myArray in an array form?
You're getting result as an array, but (in JSON terminology) it's not an array. It's an object, so use an NSDictionary. Something like this:1
NSDictionary *result = [json_dict objectForKey:#"result"];
Then you can get the inner ingredients object from that:
NSString *ingredients = [result objectForKey:#"ingredients"];
Edited as per #Bavarious' comment.
1Apologies for glaring errors, as I'm not terribly well-versed in Objective-C. You might need to allocate memory for the returned NSDictionary and NSString pointers; I'm not sure.
Here's all you need to do:
NSDictionary *json_dict = [theResponseString JSONValue];
// Use a key path to access the nested element.
NSArray *myArray = [json_dict valueForKeyPath:#"result.ingredients"];
EDIT
Whoops, Matt's right. Here's how to deal with the string value:
// Use a key path to access the nested element.
NSString *s = [json_dict valueForKeyPath:#"result.ingredients"];
NSArray *ingredients = [s componentsSeparatedByString:#", "];
Note that you might need to trim the '.' character off the last element of the array.

Match multiple strings in multiple NSArray

I need to select stories from a NSArray of XML by matching a string from an XML element against a list of strings in another NSArray
The XML contains stories, each story has three criteria, say 'Fruit', 'Veg', 'Spice', each containing a single phrase. A sample story might look like:
<story>
<title>I love cooking</title>
<fruit>Oranges</fruit>
<veg>Cauliflower</veg>
<spice>Mixed spice</spice>
<blurb>losts of text and stuff....</blurb>
</story>
I have three dictionaries of key/value pairs in a NSMutableDictionary generated from a pList
<Fruit:dictionary>
'Ripe bananas' : 1
'Green bananas' : 0
<Veg:dictionary>
'Green beans' : 1
'Cauliflower' : 0
<Spice:dictionary>
'Nutmeg' : 1
'Mixed spice' : 0
I don't know what the keys will be, and I need to match the tag in the story against the keys.
i.e. story fruit tag:'Ripe bananas' MATCHES 'Ripe bananas' in list of fruit keys
I can build three arrays of the keys using
NSMutableDictionary *fruitTagsDict = [prefsDictionary objectForKey:#"Fruits"];
NSArray *fruitTags = [fruitTagsDict allKeys];
I loop through the story XML extracting a tag
for (id myArrayElement in storyArray) {
NSString *fruitString = [NSString stringWithString:[myArrayElement fruit]];
//BOOL isTheObjectThere = [issueTags containsObject:fruitString];
NSString *vegString = [NSString stringWithString:[myArrayElement veg]];
NSString *spiceString = [NSString stringWithString:[myArrayElement spice]];
//if ([[fruitTags objectAtIndex:row] isEqualToString:fruitString]) {
//NSLog(#"Yo %#", fruitString);
// ADD TO A NEW ARRAY OF MATCHING STORIES
//}
// Fails because row is undeclared
}
Then I start to glaze out.
The isTheObjectThere line produces nil then crashes at end of loop
I've looked at:
Filter entire NSDictionaries out of NSArray based on multiple keys
Making the Code check to see if the Text in a Text box matches any of the Strings in an NSArray
It seems predicate is the answer but frankly I getting confused.
What I need to do in metacode
repeat with stories
if storyFruitTag is in fruitTagArray
OR storyVegTag is in vegTagArray
OR storySpiceTag is in spiceTagArray
Add to new array of matching stories
Hopefully I've explained enough to get some pointers, I looked into NSMutableSet and Intersect (Xcode: Compare two NSMutableArrays) but the power of too much information got to me
Here's a simple way to determine whether there are matches using key paths:
if ([prefsDict valueForKeyPath:[NSString stringWithFormat:#"Fruit.%#", storyFruitTag]] ||
[prefsDict valueForKeyPath:[NSString stringWithFormat:#"Veg.%#", storyVegTag]] ||
[prefsDict valueForKeyPath:[NSString stringWithFormat:#"Spice.%#", storySpiceTag]]) {
// one of the story's tags matches a key in one of the corresponding dictionaries
}