I have successfully downloaded and parsed (I think) the JSON data
NSURL *quoteURL = [NSURL URLWithString:#"http://www.qwoatzz.com"];
NSData *jsonData = [NSData dataWithContentsOfURL:quoteURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
However, I'm not too sure how to actually use the JSON data. If the JSON file has an array with two keys, how do I get a value from one of these keys at a specific index (the first one for example) and use that to change the text of a label (I know how to do that, it's just the JSON part I am stuck on)?
2014-10-20 19:46:10.616 Qwoatz-2[3147:454481] dataDictionary : {
count = 10;
"count_total" = 1871;
pages = 188;
posts = (
{
author = "Jason Seifer";
date = "2014-10-20 13:54:11";
id = 24317;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/1634685862_92b26b9167_o-150x150.jpg";
title = "What Employers Are Looking For in a Junior Rails Developer";
url = "http://blog.teamtreehouse.com/employers-looking-junior-rails-developer";
},
{
author = "Zac Gordon";
date = "2014-10-16 09:27:38";
id = 24296;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/brochure-site-150x150.png";
title = "When is WordPress.com the Right Solution?";
url = "http://blog.teamtreehouse.com/wordpress-com-right-solution-website";
},
{
author = "Gill Carson";
date = "2014-10-15 12:52:43";
id = 24287;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/Tahoe_team-Photo-150x150.jpg";
title = "We Are Family – The Whole Team!";
url = "http://blog.teamtreehouse.com/family";
},
{
author = "Jason Seifer";
date = "2014-10-14 15:26:11";
id = 24292;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/Chartist-Simple-responsive-charts-2014-10-14-15-24-43-150x150.jpg";
title = "Responsive Charts";
url = "http://blog.teamtreehouse.com/responsive-charts";
},
{
author = "Guil Hernandez";
date = "2014-10-13 09:28:05";
id = 24228;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/09/blend-mode-mult-150x150.jpg";
title = "Cutting-Edge CSS Features You Can Use Today";
url = "http://blog.teamtreehouse.com/cutting-edge-css-features-can-use-today";
},
{
author = "Faye Bridge";
date = "2014-10-10 09:00:45";
id = 24230;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/09/Nick-Bryan-150x150.jpg";
title = "After Just 6 Months Learning Nick is a full-time Web Developer";
url = "http://blog.teamtreehouse.com/6-months-nick-now-full-time-web-developer-major-computing-firm";
},
{
author = "Pasan Premaratne";
date = "2014-10-09 13:59:23";
id = 24250;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/Screen-Shot-2014-10-06-at-5.57.16-PM-150x150.png";
title = "Making a Network Request in Swift";
url = "http://blog.teamtreehouse.com/making-network-request-swift";
},
{
author = "Zac Gordon";
date = "2014-10-09 09:21:29";
id = 24278;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/wordpress_themes-150x150.jpg";
title = "New Course: WordPress Theme Development";
url = "http://blog.teamtreehouse.com/new-course-wordpress-theme-development";
},
{
author = "Dave McFarland";
date = "2014-10-08 13:47:55";
id = 24255;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/Screen-Shot-2014-10-06-at-1.02.40-PM-150x150.png";
title = "How to Install Node.js and NPM on a Mac";
url = "http://blog.teamtreehouse.com/install-node-js-npm-mac";
},
{
author = "Jason Seifer";
date = "2014-10-07 16:15:00";
id = 24273;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/html5-device-mockups-150x150.jpg";
title = "Device Mockups";
url = "http://blog.teamtreehouse.com/device-mockups";
}
);
status = ok;
}
An example JSON file that was parsed.
Loop through the dictionary:
for(NSString *key in dataDictionary) {
id myObject = [dataDictionary objectForKey:key];
//do something with myObject
}
An NSDictionary is not an ordered collection, so there is no guarantee that looping through a dictionary as shown above will always loop through the keys in the same order. Apple doesn't provide an ordered dictionary with Cocoa/Cocoa Touch, and generally it is a bad idea to subclass NSDictionary or NSMutableDictionary as they are part of a class cluster.
Looking at the text from your example, posts is actually an array full of dictionaries. Assuming all the keys in your example are constant across the JSON files that you will be fetching, you could retrieve it using
NSArray *posts = [dataDictionary arrayForKey:#"posts"];
This array already appears to be ordered by date. You could then get the title for each post
for(int i = 0; i < [posts count]; i++) {
NSString *title = [((NSDictionary *)(posts[i])) objectForKey:#"title"];
//do something with title
}
1) Do you know the data is an array?
the JSON file has an array with two keys,...
value from one of these keys at a specific index...
This is somewhat of a mixed metaphor for me. When I have a JSON Array or NSArray, I tend to only think of indices (since that how arrays are ordered), and when I have JSON Objects or NSDictionaries, I tend to think of keys.
So, does the return value look like this:
[ "cat", 1, "a" ]
or does the data look like this:
{
"cat": {
"count": 1,
"tag": "a"
}
}
The first example is an Array with 3 elements; the second is an Object with 1 member that itself has 2 members.
2) If the data is correctly parsed as either an NSArray, or NSDictionary ...
Then you simply need to extract the data you want, with the accessors available on either container.
E.g.
NSArray *a = ...
[a firstObject];
[a objectAtIndex:0]; // same as above
NSDictionary *d = ...
d[#"memberName"];
[d objectForKey:#"memberName"]; // same as above
You'll want to actually save that data, or pass it to be processed, instead of just invoking the accessor.
UPDATE: based on the example data updated in the question.
One method is that you could extract the data both a bit manually, and iteratively.
NSDictionary *dataDictionary = ...
NSInteger count = [[dataDictionary objectForKey:#"count"] integerValue];
NSInteger countTotal = [[dataDictionary objectForKey:#"count_total"] integerValue];
NSInteger pagesCount = [[dataDictionary objectForKey:#"pages"] integerValue];
NSString *status = [dataDictionary objectForKey:#"status"];
NSArray *posts = [dataDictionary objectForKey:#"posts"];
for (NSDictionary *post in posts) {
for (NSString *key in post) {
NSLog(#"%#: %#", key, post[key]);
}
}
When you log dataDictionary, unquoted elements that are clearly strings are strings, elements in quotes are strings, integers and other numbers are likely usable numbers, but they may be strings (depends on return format), the date will be a string (and you can use NSDate and NSDateFormatter to pretty print it), status is just a string; for posts, the '(' and ')' wrap an array, and '{','}' wrap dictionaries.
UPDATE 2:
If you really want to do advanced searching, you can use NSPredicate to filter NSDictionary's or NSArray's. For example, something like the following would work:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
#autoreleasepool {
NSDictionary *data = #{
#"stuff": #1,
#"posts": #[
#{ #"id": #1, #"title": #"one" },
#{ #"id": #2, #"title": #"two" },
#{ #"id": #3, #"title": #"three" },
#{ #"id": #4, #"title": #"four" },
#{ #"id": #5, #"title": #"five" },
]
};
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.id == %#", #4];
NSString *title = [[data[#"posts"] filteredArrayUsingPredicate:predicate] firstObject];
NSLog(#"title: %#", title);
}
}
prints
title: {
id = 4;
title = four;
}
You can use JSONModel Framework
JSONModel is a data model framework for iOS and OSX. It's written in Objective-C and helps you in several different ways.
You can read more about its key features below:
Rapidly write model code
Validation of the model's input
Atomic data
Type casting between JSON and Obj-C
Built-in data transformers
Custom data transformers
Model cascading
Convert back & forth from/to JSON
Persist model state in memory or file
Create models straight from the Internet
Automatic model compare methods
Note Please make sure your properties name match with key name in JSON
JSONModel Framework
GitHub Link
Related
I used json-framework to pull a JSON string from and URL and convert the JSON string to NSDictionary object with these two lines of code
SBJsonParser* parser = [SBJsonParser new];
NSDictionary* myDict = [parser objectWithString:resp];
My NSDicitonary has:
(
{
condition = no;
date = "2013-06-21";
"location_id" = 9;
name = Chabahil;
reason = "";
time = "03:04:22";
},
{
condition = pressure;
date = "2013-06-21";
"location_id" = 7;
name = Maitighar;
reason = "Peak Hour";
time = "03:04:13";
}
)
Now I need to access each element for example I want to get value of "name" of the second element. I couldnot figure out how to do it.
Thanks!
The JSON string contains not a dictionary, but an array (of two dictionaries).
So you would do
SBJsonParser* parser = [SBJsonParser new];
NSArray* jsonArray = [parser objectWithString:resp];
and access the values for example like
NSString *secondName = [[jsonArray objectAtIndex:1] objectForKey:#"name"];
or, using the modern subscripting syntax:
NSString *secondName = jsonArray[1][#"name"];
(Note that there already is a NSJSONSerialization class in Foundation, so unless you
have a specific reason to use SBJsonParser, you could use that as well.)
I have an array of Videos objects with, among other things, the properties id and tags.
I want to build a dictionary whose key is a tag and whose value is an array of id's.
For example, some Video objects might look like this:
Video{ id:1, tags:[funny,political,humor] }
Video{ id:2, tags:[political,america] }
I want the result dictionary to look like this:
VideosWithTags["funny":[1]; "political":[1,2]; "humor":[1]; "america":[2]]
Is there a standard algorithm to accomplish this?
Currently I'm doing something like this:
for (NSDictionary *video in videos)
{
NSNumber *videoId = [video objectForKey:#"id"];
NSArray *tags = [video objectForKey:#"tags"];
for (NSString *tag in tags)
{
NSMutableArray *videoIdsForTag = nil;
if ([videosAndTags objectForKey:tag] != nil) //same tag with videoIds already exists
{
videoIdsForTag = [videosAndTags objectForKey:tag];
[videoIdsForTag addObject:videoId];
//add the updated array to the tag key
[videosAndTags setValue:videoIdsForTag forKey:tag];
}
else //tag doesn't exist yet, create it and add the videoId to a new array
{
NSMutableArray *videoIds = [NSMutableArray array];
[videoIds addObject:videoId];
//add the new array to the tag key
[videosAndTags setObject:videoIds forKey:tag];
}
}
}
You can make this look a little cleaner by using the new literal syntax.
I think you could benefit by making your if branches do less work. e.g. You would be better of trying to retrieve the videoIds array then if it doesn't exist - create it and add it to the videosAndTags object and then the code after this point can be consistent with no duplication of logic
for (NSDictionary *video in videos) {
NSNumber *videoId = video[#"id"];
NSArray *tags = video[#"tags"];
for (NSString *tag in tags) {
NSMutableArray *videoIds = videosAndTags[tag];
if (!videoIds) {
videoIds = [NSMutableArray array];
videosAndTags[tag] = videoIds;
}
// This is the only line where I manipulate the array
[videoIds addObject:videoId];
}
}
NSArray* videos =
#[#{ #"id" : #1, #"tags" : #[ #"funny", #"political", #"humor" ] },
#{ #"id" : #2, #"tags" : #[ #"political", #"america" ] } ];
NSMutableDictionary* videosAndTags = [NSMutableDictionary new];
// find distinct union of tags
NSArray* tags = [videos valueForKeyPath: #"#distinctUnionOfArrays.tags"];
// for each unique tag
for( NSString* tag in tags )
{
// filter array so we only have ones that have the right tag
NSPredicate* p = [NSPredicate predicateWithFormat: #"tags contains %#", tag];
videosAndTags[ tag ] = [[videos filteredArrayUsingPredicate: p] valueForKeyPath: #"id"];
}
Here is another approach using NSPredicate and valueForKeyPath.
I don't used them often, but sometimes they can prove to be useful.
(I think they call this the Functional Programming style of things, but I am not so sure)
NSPredicate reference
Key Value Coding
I have a NSMutableArray and a NSArray. Both consist of elements that are NSDictionarys themselves.
A sample structure of both is as follows:
NSMutableArray
[
{
objectId = 4274;
name = orange;
price = 45;
status = approved;
},
{
objectId = 9035;
name = apple;
price = 56;
status = approved;
},
{
objectId = 7336;
name = banana;
price = 48;
status = approved;
}
.
.
.
.
]
and NSAraay is
NSArray
[
{
objectId = 4274;
name = orange;
price = 106;
status = not_approved;
},
{
objectId = 5503;
name = apple;
price = 56;
status = approved;
}
]
What I want is to merge these two arrays so that, if any element in NSArray has same objectId as any element in NSMutableArray, element in NSArray should overwrite on element in NSMutableArray.
So in this case final merged array should look like this
MergedArray
[
{
objectId = 4274;
name = orange;
price = 106;
status = not_approved;
},
{
objectId = 9035;
name = apple;
price = 56;
status = approved;
},
{
objectId = 7336;
name = banana;
price = 48;
status = approved;
},
{
objectId = 5503;
name = apple;
price = 56;
status = approved;
}
.
.
.
.
]
Only way to this I know is to iterate through both arrays and merge. Is there any better way? Any help will be greatly appreciated.
EDIT:
Following dasblinkenlights suggestion, I did it following way
-(NSMutableArray*)mergeTwoArray:(NSArray*)array1 :(NSArray*)array2
{
//array1 will overwrite on array2
NSSet* parentSet = [NSSet setWithArray:array2];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (NSDictionary *item in parentSet)
[dict setObject: item forKey: [item objectForKey:#"objectId"]];
NSLog(#"initial dictionary is %#",dict);
for (NSDictionary *item in array1)
[dict setObject: item forKey: [item objectForKey:#"objectId"]];
NSLog(#"final dictionary is %# with all values %#", dict,[dict allValues]);
return [NSMutableArray arrayWithArray:[dict allValues]];
}
Since your objectId value can be used as a unique key, you could potentially create an NSMutableDictionary on the side, populate it with NSDictionary objects from the first array using objectId value as the key, go through the second array, do the overwrites, and finally harvest the values of the resultant NSMutableDictionary into your final output.
Note that this approach might be helpful only if your arrays are relatively long (1000+ items). If you deal with 10..100 items, I wouldn't bother, and code two nested loops as you suggested.
I would recommend iterating through both arrays and merging, but first sort them. Once sorted, you can merge two arrays in O(N) time. For most purposes, this is about as fast as you can get, and it requires very little code.
If they are large enough that the sort is a bottleneck, you could get fancy using an NSSet: put the (elements of the) over-riding array in the set first, and then add the elements of the original array. But you would have to implement an isEqual method for your elements. In this case, it would mean that your elements would no longer be NSDictionary, but rather a class that inherits from NSDictionary but implements the isEqual method to compare the object ID fields.
Because NSSet gives amortized constant time access, this would be faster, if the arrays are large, since there is no sort phase.
all info is an __NSCFDictionary and I'm trying to get access just to the "ratings" key (the 3rd key). Any idea how I would go about grabbing the data from that field?
thanks for any help
my NSLog output from NSLog(#"allinfo description %#", allInfo.description); is the following
allinfo description __NSCFDictionary
2012-03-14 11:50:28.056 Project[1173:11603] key = results -> obj = (
{
geometry = {
location = {
lat = "37.787565";
lng = "-122.409593";
};
};
icon = "http://maps.gstatic.com/mapfiles/place_api/icons/bar-71.png";
id = 6ea9093f35b4b0cf5cfa21b7dd40cb28d5aa5c5c;
name = "Ruby Skye";
rating = "3.4";
reference = "CmRfAAAApDkEMtz2k6RQTmdeamVqY3KrXhpyzLoHc7EUDMCCUi3qD6PsYQE8qO2QjiUohpJIFtub_XUcYqPOt_LSUbILHjMzeHiacXDCSSKhybRB4VTy7-Oi72nibqwI3NmxnoxNEhB1I7mUheViH2V2ESkkmHbwGhS1G-LDz0PfJggIt8sZ2YO_BOILkQ";
types = (
"night_club",
bar,
establishment
);
vicinity = "420 Mason Street, San Francisco";
},
{
geometry = {
location = {
lat = "37.790237";
lng = "-122.404676";
};
};
icon = "http://maps.gstatic.com/mapfiles/place_api/icons/bar-71.png";
id = bf9fc5861d0ab56a337ded8d8c4b49e62709e3e5;
name = "The Irish Bank";
rating = "3.7";
reference = "CnRlAAAAs0ggA4T1ri7H68CE7N1IWhfmY6EcED9pMNhvKhrFRUBFJLw73URrqoBpyGB585u7xDwtbmBV53eBj3rXpMlFI52OKz2Uv5-mIKT5pjFXCZJoTiux_SPoWQWFPCYoePyxhl6NFvmLWr-K-Jr7UnapWRIQNSuYGOFKgYg3yHTtoGq1KxoUqT4O-yIncvKbDJz8IygsdL7P2Go";
types = (
bar,
restaurant,
food,
establishment
);
vicinity = "10 Mark Lane, San Francisco";
},
You have a partial console snippet that looks like an NSArray; but it appears to be an NSDictionary when you log [allInfo class].
Can you try this:
[allInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(#"key = %# -> obj = %#",key,obj);
}];
This should log the keys and objects and help elucidate the structure of the NSDictionary you are working with.
Update after OP added log output:
// This should log all of the ratings
NSArray *results = [allInfo objectForKey:#"results"];
[results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *ratingStr = [obj objectForKey:#"rating"];
NSLog(#"rating = %#",ratingStr);
}];
The output (allinfo.description) looks like an NSArray with NSDictionaries inside of it but, [allinfo class] says it's an NSDictionary however there doesn't appear to be keys for each element. Apple's documentation specifies this about the keys when calling -description:
If each key in the dictionary is an NSString object, the entries are listed in ascending order by key, otherwise the order in which the entries are listed is undefined.
I'd try using the -allKeys to get an NSArray of all the keys. If they aren't NSStrings then you'll need to figure out what it is in order to access the elements in this dictionary.
The log outPut you posted is an Array.You can access the "rating" by
NSString *ratingValue = [[allInfo.description objectAtIndex:0]objectForKey:#"rating"];
I have array made from JSON response.
NSLog(#"%#", arrayFromString) gives the following:
{
meta = {
code = 200;
};
response = {
groups = (
{
items = (
{
categories = (
{
icon =
"http://foursquare.com/img/categories/parks_outdoors/default.png";
id = 4bf58dd8d48988d163941735;
and so on...
This code
NSArray *arr = [NSArray arrayWithObject:[arrayFromString valueForKeyPath:#"response.groups.items"]];
gives array with just one element that I cannot iterate through. But if I write it out using NSLog I can see all elements of it.
At the end I would like to have an array of items that I can iterate through to build a datasource for table view for my iPhone app.
How would I accomplish this?
EDIT:
I have resolved my issue by getting values from the nested array (objectAtIndex:0):
for(NSDictionary *ar in [[arrayFromString valueForKeyPath:#"response.groups.items"] objectAtIndex:0]) {
NSLog(#"Array: %#", [ar objectForKey:#"name"]);
}
First, the data structure you get back from the JSON parser is not an array but a dictionary: { key = value; ... } (curly braces).
Second, if you want to access a nested structure like the items, you need to use NSObject's valueForKeyPath: method. This will return an array of all items in your data structure:
NSLog(#"items: %#", [arrayFromString valueForKeyPath:#"response.groups.items"]);
Note that you will loose the notion of groups when retrieving the item objects like this.
Looking at the JSON string you posted, response.groups.items looks to be an array containing one item, a map/dictionary containing one key, "categories." Logging it out to a string is going to traverse the whole tree, but to access it programmatically, you have to walk the tree yourself. Without seeing a more complete example of the JSON, it's hard to say exactly what the right thing to do is here.
EDIT:
Traversing an object graph like this is not that simple; there are multiple different approaches (depth-first, breadth-first, etc,) so it's not necessarily something for which there's going to be a simple API for you to use. I'm not sure if this is the same JSON library that you're using, but, for instance, this is the code from a JSON library that does the work of generating the string that you're seeing. As you can see, it's a bit involved -- certainly not a one-liner or anything.
You could try this, which I present without testing or warranty:
void __Traverse(id object, NSUInteger depth)
{
NSMutableString* indent = [NSMutableString string];
for (NSUInteger i = 0; i < depth; i++) [indent appendString: #"\t"];
id nextObject = nil;
if ([object isKindOfClass: [NSDictionary class]])
{
NSLog(#"%#Dictionary {", indent);
NSEnumerator* keys = [(NSDictionary*)object keyEnumerator];
while (nextObject = [keys nextObject])
{
NSLog(#"%#\tKey: %# Value: ", indent, nextObject);
__Traverse([(NSDictionary*)object objectForKey: nextObject], depth+1);
}
NSLog(#"%#}", indent);
}
else if ([object isKindOfClass: [NSArray class]])
{
NSEnumerator* objects = [(NSArray*)object objectEnumerator];
NSLog(#"%#Array (", indent);
while (nextObject = [objects nextObject])
{
__Traverse(nextObject, depth+1);
}
NSLog(#"%#)", indent);
}
else
{
NSLog(#"%#%#",indent, object);
}
}
void Traverse(id object)
{
__Traverse(object, 0);
}