iOS - Convert a JSON object to an ordered array - ios

I have a JSON file with key/value-pairs for a list of countries and their country-codes. I'm reading it with Objective-C and want to present it in a UIPicker.
Problem is, when I store the JSON data into a NSDictionary, the original order is lost and the order of the UIPicker elements is pretty arbitrary (I know, dictionaries don't have a particular order).
What's the easiest way to preserve to original order? Do I have to create two files, one with the country-codes and one with the country names in the same order and read both into an array? - I really wanna avoid that...
[Edit]
As per request, an excerpt of the JSON-Data (stored in a local file)
{"af":"Afghanistan",
"al":"Albania",
"dz":"Algeria",
"as":"American Samoa",
"ad":"Andorra",
"ao":"Angola",
"ai":"Anguilla",
"aq":"Antarctica",
"ag":"Antigua and Barbuda",
"ar":"Argentina",
"am":"Armenia",
"aw":"Aruba",
"ac":"Ascension Island (Atlantic ocean)",
"au":"Australia",
"at":"Austria",
"az":"Azerbaijan",
"bs":"Bahamas",
"bh":"Bahrain",
"bd":"Bangladesh",
"bb":"Barbados",
"by":"Belarus",
"be":"Belgium",
"bz":"Belize",
"bj":"Benin",
"bm":"Bermuda",
"bt":"Bhutan",
"bo":"Bolivia",
"ba":"Bosnia",
"bw":"Botswana",
"bv":"Bouvet Island (Atlantic ocean)",
"br":"Brazil",
... }
[Edit 2]
I'm using this code to read the JSON data and convert it to a NSDictionary:
NSURL *jsonPath = [[NSBundle mainBundle] URLForResource:#"countries" withExtension:#"json"];
NSString *stringPath = [jsonPath absoluteString];
NSData *countriesJSON = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
NSDictionary *parsedJson = [NSJSONSerialization JSONObjectWithData:countriesJSON options:0 error:nil];

You can change your JSON data to
[
{"af":"Afghanistan"},
{"al":"Albania"},
{"dz":"Algeria"},
{"as":"American Samoa"},
{"ad":"Andorra"},
{"ao":"Angola"},
{"ai":"Anguilla"},
...
]
Then use the code like:
NSURL *jsonPath = [[NSBundle mainBundle] URLForResource:#"sample" withExtension:#"json"];
NSData *countriesJSON = [NSData dataWithContentsOfURL:jsonPath];
NSMutableArray *countryArray = [NSJSONSerialization JSONObjectWithData: countriesJSON options:NSJSONReadingMutableContainers error:nil];
The JSON is converted to a ordered array called countryArray. the elements of the countryArray is a NSDictionary instance.

If order is important and you have access to the JSON file, then you could change your JSON to be an array like so:
[
["Afghanistan", "AF"],
["Bangladesh", "BD"]
]
Then when you deserialize it, order will be preserved because the deserialized object will be an array of arrays.
If you don't have access to your JSON file then you could create this array of arrays from your Dictionary in code.
Edit
If you want both quick lookups and an ordering then it's tough to avoid using 2 data structures. With the JSON file you've submitted, it seems you want an array:
NSArray *countriesSorted = [parsedJson.allValues sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
However, it would be better for you to switch the order of values in your json file to be country-name:country-code. Then you could do
NSArray *countryCodesSorted = [parsedJson.allKeys sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSString *firstCountryName = parsedJson[countryCodesSorted[0]];

The issue of order and dictionaries often occurs when the dictionary needs to be displayed in a tableview. The established solution to this is to keep a separate array of dictionary keys alongside the dictionary. You can re-order the array as you (or the user) likes and do a two-stage fetch of dictionary entries via the array. This allows you to keep your original JSON format and internal storage properties/instance variables.
YourClass.m:
#interface MyClass ()
#property NSMutableArray *dictOrder;
#end
#implementation MyClass
- (void)getData
{
self.dict = // Get dictonary from JSON
self.dictOrder = [[self.dict allKeys] mutableCopy];
[self.dictOrder sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
}
// UITableViewDataSource example
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Normal cell re-use stuff
NSString *key = self.dictOrder[indexPath.row];
NSString *country = self.dict[key];
// Populate cell
}
#end

Related

How to change value in object of object from NSMutableDictionary? (Objective-C) [duplicate]

I'm reading into my application a plist which at the top level is an array. It's simple enough to make sure the array starts as mutable
self.plistData = [[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"myDataSource" ofType:#"plist"]] mutableCopy];
Within each element of the array is another array, each of those contains a dictionary.
Based on a tablecell selection I'm changing attributes for the dictionary at the selected index:
[self.cellDescriptors[indexPath.section][indexOfTappedRow] setValue:#"YES" forKey: #"isSelected"];
I'm getting the error '-[__NSCFDictionary removeObjectForKey:]: mutating method sent to immutable object' when I attempt to change a dictionary value. The error makes sense if the NSDictionary read in by the plist is immutable.
Is there any way to read in content from a plist and make sure any arrays or dictionaries are read in as mutable versions?
The simplest approach is to use NSPropertyListSerialization and passing the proper options to get mutable containers.
NSString *path = [[NSBundle mainBundle] pathForResource:#"myDataSource" ofType:#"plist"];
NSData *data = [NSData dataWithContentsOfFile:path];
NSMutableArray *array = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainers format:nil error:nil];
self.plistData = array;
Ideally you will make use of the error parameter and do proper error checking but this will get you started.
This code will load the plist and return a mutable array and every contained array and dictionary will also be mutable.

NSPropertyListSerialization returning nil instead of serialized data

I am putting two NSMutableArray objects into an NSDictionary and trying to serialize, but the method call is returning nil. One array, addresses, is an array of NSString objects. The other, engines is an array of objects that each contain several data types. I am attempting to serialize using the following code:
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:engAddr forKey:#"engAddr"];
[dictionary setObject:trainList forKey:#"engines"];
NSData *data = [NSPropertyListSerialization dataFromPropertyList:dictionary
format:NSPropertyListXMLFormat_v1_0
errorDescription:&error];
Stepping through, the debugger shows the arrays are properly added to the dictionary, but after the line that should serialize the dictionary it shows data = (NSData *) nil.
Where am I going wrong? Thank you for your help!
What kind of objects does engines contain?
Plist supports only specific objects below.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html#//apple_ref/doc/uid/10000048i-CH3-54303
If you want to serialize a custom object, convert it to NSData by NSKeyedArchiver.
To do that, objects must conform NSCoding protocol.

Parse json Data in iOS

I know it's a common question, but I am stuck with is API
How to Parse data from this API : http://dl.bahramradan.com/api/1/get/ig
it contain 20 Object and in every object there are 3 other Objects called "image",date" and "caption"
how can I store all "date" values in an NSMUtableArray in ios?
I did this :
NSString *urlStr = [NSString stringWithFormat:#"http://dl.bahramradan.com/api/1/get/ig"];
NSURL *url = [NSURL URLWithString:urlStr];
NSData *json = [NSData dataWithContentsOfURL:url];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:json options:0 error:NULL];
NSArray *dateArray = [dict objectForKey:#"date"];
But when I run my app, it crashs on the last line, what is wrong ?
I did not check, if your JSON is valid. But there is one obvious mistake in your code: If the JSON consists of 20 objects, I assume those being contained in an array, rather than in a dict!
So first thing to change is
NSArray *array = [NSJSONSerialization JSONObjectWithData:json options:0 error:NULL];
Then, you want to extract the 'date' values for all items and combine these in another array.
Easiest way to achieve that, is by using a KVC Collection Operator
NSArray *dateArray = [array valueForKeyPath:#"#unionOfObjects.date"];
So, what '#unionOfObjects.date' does, is: going through all the objects in the array, look for each of their 'date' value and combine them in the returned array.
Check out this excellent post about KVC Collection Operators!

Replacing Object in Dictionary in an Array

I have an array structure as follows:
NSMutableArray topArray{
NSMutableArray middleArray{
NSMutableArray lowerArray{
NSMutableDictionary dict{
}
}
}
}
The array structure is filled with some data that I retrieve from the web is JSON format.
I am trying to edit one of the objects in the NSMutableDictionary as follow:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *lowerArray = [[self middleArray] objectAtIndex:[indexPath section]];
NSMutableDictionary *dict = [lowerArray objectAtIndex:[indexPath row]];
[dict setObject:[NSNumber numberWithInt:1] forKey:#"key"];
[[self tableView] reloadData];
}
The data within each of the arrays is correct (I have checked with print statements), however, when I try to change the object in the dict I get the following error:
reason:
'-[__NSCFDictionary setObject:forKey:]: mutating method sent to
immutable object'
I need the object in the dictionary to be changed within the array structure.
Could this be an issue with the JSON data since when topArray is first initialised with the JSON data the middle and lower arrays are in the form of just NSArray's?
Sorry if this is confusing, I will try to clarify more if you have any questions.
Thanks in advance for your help.
If you're using NSJSONSerialization, you can pass NSJSONReadingMutableContainers to the options parameter of +JSONObjectWithData:options:error:, and all of the parsed dictionaries and arrays will be mutable.
NSMutableArray *topArray = [NSJSONSerialization JSONObjectWithData:webServiceData
options:NSJSONReadingMutableContainers
error:nil];
Just use NSMutableDictionary class instead of just NSDictionary for the moduleDict variable. It is easily done when parsing JSON objects. If no - create one like this:
NSMutableDictionary *newDict = [NSMutableDictionary dictionaryWithDictionary: moduleDict];
I doubt that you are really dealing with NSMutableDictionary on the lowest level. Do you have the output from these print statements for us?
Usually the structures generated by JSON deserialisation are of immutable type. And that is exactly what your error message is telling.

NSDictionary filled with JSON data

I call an URL that returns me JSON (I use JSONKit). I convert it to a NSString that is this way:
[{"name":"aaaaaa","id":41},{"name":"as","id":23},...
And so on. I want to fill an UIPickerView with only the "name" part of the JSON. But, when the user selects a name, i need the "id" parameter, so i've thought to fill a NSDictionary with the JSON (setValue:id for key:name), so i can get the value picked by the user, and get the id from the dictionary. how could I fill an array with only the "name" of the JSON?
Im a bit lost with the JSONKit library, any guidance? Thank you.
First of all I don't think that its a good idea to have name as key in a dictionary, since you can have many identical names. I would go for id as key.
Now, what you could do is:
NSString *myJson; //Suppose that this is the json you have fetched from the url
id jsonObject = [myJson objectFromJSONString];
// Now you have an array of dictionaries
// each one having 2 key/value pairs (name/id)
NSArray *names = [jsonObject valueForKeyPath:#"name"];
NSArray *ids = [jsonObject valueForKeyPath:#"id"];
// Now you have two parallel arrays with names / ids
Or you could just iterate your json object and handle the data yourself:
for (id obj in jsonObject)
{
NSString *name = [obj valueForKey:#"name"];
NSNumber *id = [obj valueForKey:#"id"];
// Do whatever you like with these
}

Resources