I need to access a property within a dictionary and I am having a difficult time doing so.
Dictionary:
{
Id = "123";
Animal = {
Id = "456";
Type = "Dog";
Sound = "Bark";
}
}
Code:
NSString *animalType = dictionary[#"Animal.Type"];
All I get back is (null). What would be the proper way to call this in order to get "Dog"?
Try
NSString *animalType = dictionary[#"Animal"][#"Type"];
Or
NSString *animalType = [[dictionary objectForKey:#"Animal"] objectForKey:#"Type"];
Related
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
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.)
This example is contrived, but it shows my point.
So, if I have an object graph like the following:
{
sex = male;
uid = 637650940;
work = ({
employer = {
id = 116420715044499;
name = "Software Engineer";
};
"end_date" = "0000-00";
"start_date" = "0000-00";
}, {
employer = {
id = 188733137832278;
name = "Apple";
};
});
},
//Some more objects
(This is an NSArray containing NSDictionarys that have an object of type NSArray).
The key field is work. I want a Key Path that will take the first object in the work array.
If I do this:
NSArray* work = [outerArrayObject objectForKey: #"work"];
id name = [work valueForKeyPath: #"employer.name"];
I get an array containing each name (In the above case, Software Engineer & Apple). Is there a collection operator or something to return the first object? Bonus points if you can develop a Key Path to sort each work by start_date also :)
#PauldeLange - Your answer and links were helpful.
The following simpler version works too (at least as of Xcode 6)
id name = [work valueForKeyPath: #"employer.name.#firstObject”];
In the above 'firstObject' refers to the predefined method on NSArray. If the second object is needed, you can define the following:
#implementation NSArray (CustomKVOOperators)
- (id) secondObject {
return [self count] >=2 ? self[1] : nil;
}
#end
And use:
id name = [work valueForKeyPath: #"employer.name.#secondObject”];
Well to answer my own question, one way to do it is this:
1) Define the following category
#implementation NSArray (CustomKVOOperators)
- (id) _firstForKeyPath: (NSString*) keyPath {
NSArray* array = [self valueForKeyPath: keyPath];
if( [array respondsToSelector: #selector(objectAtIndex:)] &&
[array respondsToSelector: #selector(count)]) {
if( [array count] )
return [array objectAtIndex: 0];
else
return nil;
}
else {
return nil;
}
}
#end
2) Use this KeyPath syntax
NSArray* work = [outerArrayObject objectForKey: #"work"];
id name = [work valueForKeyPath: #"#first.employer.name"];
Thanks to this clever person.
I was trying a piece of code from CS193P course (Objective-C). I noticed something in the way that the compiler works. An NSMutableArray called photos was added to an NSMutableDictionary, photosByPhotographer. Later on, a change was made to photos without any changes to photosByPhotographer. When I logged photosByPhotographer, the change was automatically applied to it and it did not need any extra lines of code!
I wonder how the compiler makes this work? Any materials to read from?
The code is as follows:
- (void)updatePhotosByPhotographer
{
NSMutableDictionary *photosByPhotographer = [NSMutableDictionary dictionary];
for (NSDictionary *photo in self.photos) {
NSString *photographer = [photo objectForKey:FLICKR_PHOTO_OWNER];
NSMutableArray *photos = [photosByPhotographer objectForKey:photographer];
if (!photos) {
photos = [NSMutableArray array];
[photosByPhotographer setObject:photos forKey:photographer];
NSLog(#"photosByPhotographer in if: %#", photosByPhotographer);
}
[photos addObject:photo];
NSLog(#"photosByPhotographer after if: %#", photosByPhotographer);
}
self.photosByPhotographer = photosByPhotographer;
}
The NSLog() result is as follows:
2012-07-20 20:05:57.618 Shutterbug[453:f803] photosByPhotographer in if: {
Dowbiggin = (
);
}
2012-07-20 20:05:57.620 Shutterbug[453:f803] photosByPhotographer after if: {
Dowbiggin = (
{
accuracy = 16;
context = 0;
dateupload = 1342836026;
description = {
"_content" = "";
};
farm = 9;
"geo_is_contact" = 0;
"geo_is_family" = 0;
"geo_is_friend" = 0;
"geo_is_public" = 1;
id = 7612787270;
isfamily = 0;
isfriend = 0;
ispublic = 1;
latitude = "37.307085";
longitude = "-121.900395";
originalformat = jpg;
originalsecret = 052e70d412;
owner = "22751315#N05";
ownername = Dowbiggin;
"place_id" = cils8sJUV7MeXHwt9A;
secret = 4437007c99;
server = 8161;
tags = "square squareformat iphoneography instagramapp uploaded:by=instagram foursquare:venue=49f13597f964a5209c691fe3";
title = "My little goofball";
woeid = 55971033;
}
);
}
That's because in Cocoa, you are using objects and pass by reference.
Imagine this code:
NSMutableArray *a, *b;
a = [NSMutableArray new];
[a addObject:#"A"]; // a contains #"A"
b = a; // b is assigned to the same object
[b addObject:#"B"]; // a and b contain #"A", #"B"
NSLog(#"a = %p and b = %p", a, b); // same values
The variables a and b point to the same object. You can also see that by comparing the pointer values.
However, if we do the following:
NSMutableArray *a, *b;
a = [NSMutableArray new];
[a addObject:#"A"]; // a contains #"A"
b = [[a mutableCopy] autorelease]; // b is assigned to a COPY of the object
[b addObject:#"B"]; // b contains #"A", #"B", while a still contains #"A"
NSLog(#"a = %p and b = %p", a, b); // NOT the same values
b is assigned to a copy of a (not the original), so b points to an other object. You can check by comparing the addresses of the objects.
The photos arrays is stored by reference in the NSDictionary, not as a copy. Therefore, if you change the underlying array, you will see that when you access the dictionary.
This is why it can be dangerous to provide some mutable types by reference instead of storing copies of them.
You need to understand how pointers and references work. Basically rather than variables in your code being actual full structures and objects, generally they are references or pointers to those structures. You can visualise the concept in a number of ways, but perhaps one that's useful is to think of a variable such as:
NSDictionary *myDict;
being really just a number which is the memory address of the myDict object. Here's a visualisation of how it works:
Your NSDictionary reference points to an object in memory which itself holds multiple references to other objects, which can be changed without affecting the dictionary itself. In this example I've shown an NSArray having a new object added to it. The reference to the array from the dictionary remains the same. There is only ever one instance of the array and the objects in it at any time.
When you call NSLog on an NSDictionary object then its method description is called.
In the implementation of the method description, NSDictionary is calling the description method of each value (object) of all its keys.
So when a value (object) changes so does the output of its description method change.
That is why it is reflected when calling NSLog on an NSDictionary object again.
I'm trying to get a string in my call from an array. But have no luck. It's being passed to another class.
MainClass.h
#import First
#class First;
#interface MainClass : UIViewController{
NSMutableArray *listArray;
}
///////////////////
MainClass.m
First *first = [[First alloc] init];
listArray = [[NSMutableArray alloc]init];
[listArray addObject:#"first"];
[listArray addObject:#"second"];
int Aslot = 0;
int sumA;
float level = 5, insample = 10;
NSString *slotA =[listArray objectAtIndex:ASlot];
sumA = [slotA out:insample andlevels:level];
/////////
First.h
-(float)out:(float)insample andlevels:(float)level;
First.m
-(float)out:(float)insample andlevels:(float)level{
float outsample = insample + 10 * level;
return outsample;
}
I want slotA (the Class) to equal a string from the array "first" or "second", so it can call the method.
I have a table which when I select first, it sends samples and other parameters to another class where it does processing then returns back to MainClass.
sumA = [first out:insample andlevels:level];
But I'm getting an error saying that NSString may not respond to my parameters out:andlevels. Please help..
Edit:
If I understand correctly, you want to dynamically create an instance of a class whose name you have stored as an NSString. You can do that this way:
int Aslot = 0;
int sumA;
float level = 5, insample = 10;
NSString *className = [listArray objectAtIndex:Aslot];
Class sampler = objc_getClass([className UTF8String]);
id instance = [[sampler alloc] init];
sumA = [instance out:insample andlevels:level];
// Do whatever you want with sumA here.
[instance release];
If you're doing this a lot, you'll probably want to either keep the instance around or store the classes in the array instead of their names, depending on exactly what you are doing.