iOS and JSON nesting issue - objective-c

I am working on an app for a game server company and part of the app requires the user to see a list of his or her game servers and whether or not they are online, offline, how many players on them, the server name, etc. This data is all found in a JSON file hosted on the web updated from a MySQL database.
Using the code below, I can't seem to get it working. Now, please understand it is one of my first times working with JSON and have no choice as this is what the client requested. I talked to my partner but he couldn't seem to debug the issue himself.
The error I get is something like:
No visible #interface for 'NSArray' declares the selector 'objectForKey:'
I've tried several versions of the code, all with no success.
It would be much appreciated if you could please help me debug this code and get it working along with the commented out section near the bottom updating the TableViewCells with the server name, players online, and status (0=offline, 1=online, 2=busy, 3=suspended, -1=unable to start).
Please note that the format of the JSON file must remain as is and is only possible to make very minor changes.
Thank you,
Michael S.
My header file:
http://pastebin.com/EkuwVSmY
My main file:
http://pastebin.com/09Ju0uDu
My JSON file:
{
"status": "OK",
"error": "",
"debug": "2 server(s)",
"result": {
"servers": [
{
"id": 1,
"title": "Test",
"players": 0,
"slots": 10,
"status": 3
},
{
"id": 2,
"title": "Creative Spawn",
"players": 0,
"slots": 5,
"status": -1
}
]
}
}
The block that gives me the error is:
NSArray *serverResults = [[news objectForKey:#"result"] objectForKey:#"servers"];
if ([[[serverResults objectAtIndex:indexPath.row] objectForKey:#"status"] isEqual:#"1"]) {
serverPlayers.text = #"10000000";
}

The error you get points out that you are working on an NSArray instead of a NSDictionary. There is no method objectForKey defined on an NSArray. So what you could do is to debug your Webservice response and check the Data types.
Regarding at your output from JSON it should be like that:
NSDictionary (Keys: status, error, debug, result, servers)
servers is an NSArray which has NSDictionaries as elements.
To get the title of a server:
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
NSDictionary *firstServer = [servers objectAtIndex:0]; // I fetch here the first server
NSString *titleOfFirstServer = [firstServer objectForKey:#"title"];
NSNumber *statusOfFirstServer = [NSNumber numberWithInt:[[firstServer objectForKey:#"status"] intValue];
To iterate over all servers you should do it like that:
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
for(NSDictionary *server in servers) {
NSString *title = [server objectForKey:#"title"];
NSNumber *status = [NSNumber numberWithInt:[[server objectForKey:#"status"] intValue];
NSLog(#"%#%d", title, [status intValue]);
}

Related

iOS objective-C: how can I get useful information out of object description?

My question may be naive, I guess, since object description is usually used for debug and output in NSLog. I also have to admit that my approach of attempting use object.description may be wrong. However, I did find in my case, information in description is just what I need. If I can fetch out the part easily from the description.
Okay, here is my code and what I need is user first name (not username):
self.facebookAccountStore = [[ACAccountStore alloc] init];
ACAccountType* facebookAccountType = [self.facebookAccountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
//[self.facebookAccountStore requestAccessToAccountsWithType:facebookAccountType withCompletionHandler:^(BOOL granted, NSError* e) {
[self.facebookAccountStore requestAccessToAccountsWithType:facebookAccountType options:facebookOptions completion:^(BOOL granted, NSError* e) {
if (granted) {
NSArray* accounts = [self.facebookAccountStore accountsWithAccountType:facebookAccountType];
self.facebookAccount = [accounts lastObject];
NSLog(#"acct description: %#", self.facebookAccount.description);
NSLog(#"acct type: %#", self.facebookAccount.accountType);
NSLog(#"acct credential: %#", self.facebookAccount.credential);
NSLog(#"acct identifier: %#", self.facebookAccount.identifier);
NSLog(#"acct username: %#", self.facebookAccount.username);
} else { //.....
}
account description gives out:
acct description:
objectID: x-coredata://F8123001-FB33-48D4-B1A7-EXXXX1243XXXX/Account/p10
enabledDataclasses: {(
"com.apple.Dataclass.Contacts",
"com.apple.Dataclass.Calendars"
)}
enableAndSyncableDataclasses: {(
)}
properties: {
fullname = "LOOKAT HERE";
uid = 100004223213342323;
}
parentAccount: (null)
owningBundleID:(null)
type:com.apple.facebook
identifier: EF34399A-8577-459B-BE5E-FD12132SEDSFE
accountDescription: Facebook
username: pspped.ok#mailserver.com
check out this part:
properties: {
fullname = "LOOKAT HERE";
uid = 100004223213342323;
}
This is exactly what I need. If I use this, I don't have to do SLRequest and SLRequestMethodGET etc to get user name (which I didn't figure out yet, another motivation why I am attempted to use object description).
My question is whether it's wrong to parse description and get out fullname property? If wrong, why?
Following question is: Wrong or not, how can I get fullname property of description gracefully (such as convert description to something that uses dot and dot to get property, not to chop the string and get that fullname part), because account.description.fullname or account.fullname so doesn't work although account.username works.
Thanks for your time/input.
It is wrong to parse the description. It's wrong because the description has no documented format. It may change arbitrarily from one instance of an object to the next and from one version of the OS to the next. So your code is likely to break. By relying on undocumented behaviour you're also in breach of your iOS Developer Programme agreement with Apple, giving them the right to pull your apps at any time.
Furthermore if for some reason you're absolute desperate to use undocumented API then you might as well use [self.facebookAccount.description valueForKey:#"fullName"]; it'll be just as robust to OS changes without you trying to second guess how Core Data object descriptions are output.
This is not the most efficient way but it works well for me:
ACAccount *acc = theAccount;
NSArray *parse = [acc.description componentsSeparatedByString:#"uid = "];
parse = [[parse objectAtIndex:1] componentsSeparatedByString:#";"];
NSString *UID = [parse objectAtIndex:0];
parse = [acc.description componentsSeparatedByString:#"fullname = \""];
parse = [[parse objectAtIndex:1] componentsSeparatedByString:#"\";"];
NSString *name = [parse objectAtIndex:0];

iOS, Remote server search with RestKit

I'm working on an app where I want to make a remote search to a server. I want RestKit to save the retrieved data to the database. I first perform a local search (which currently works) then I want to make the remote search and then update a table view with the new results.
I'm having two problems, 1. how should my mapping look like and 2. the json returns an array with two different kinds of objects.
The URL looks like this:
search.json?search=[search string]
The JSON it returns looks like this:
[
{
"event": {
"id": 2,
[...]
},
{
"news": {
"id": 16,
[...]
}
Where event and news is two kind of objects.
In my app I have three models, Post (abstract entity and superclass) NewsPost (subclass to Post) and Event (subclass to Post).
My mappings looks like this:
RKManagedObjectMapping* newsMapping = [RKManagedObjectMapping mappingForClass:[NewsPost class] inManagedObjectStore:objectManager.objectStore];
newsMapping.primaryKeyAttribute = #"newsId";
newsMapping.rootKeyPath = #"news";
[newsMapping mapKeyPath:#"id" toAttribute:#"newsId"];
RKManagedObjectMapping *eventMapping = [RKManagedObjectMapping mappingForClass:[CalendarEvent class] inManagedObjectStore:objectManager.objectStore];
eventMapping.primaryKeyAttribute = #"calendarId";
eventMapping.rootKeyPath = #"calendars";
[eventMapping mapKeyPath:#"id" toAttribute:#"calendarId"];
// These two works.
[objectManager.mappingProvider setObjectMapping:newsMapping forResourcePathPattern:#"/package_components/1/news"];
[objectManager.mappingProvider setObjectMapping:eventMapping forResourcePathPattern:#"/package_components/1/calendars"];
// I don't know how these should look/work.
// Since the search word can change
[objectManager.mappingProvider setObjectMapping:eventMapping forResourcePathPattern:#"/package_components/1/search\\.json?search="];
[objectManager.mappingProvider setObjectMapping:newsMapping forResourcePathPattern:#"/package_components/1/search\\.json?search="];
My search code looks like this (local search works):
- (void)setUpSearch
{
if (self.searchField.text != nil) {
[self.posts removeAllObjects];
[self.events removeAllObjects];
[self.news removeAllObjects];
// Search predicates.
// Performs local search.
NSPredicate *contactNamePredicate = [NSPredicate predicateWithFormat:#"contactName contains[cd] %#", self.searchField.text];
NSPredicate *contactDepartmentPredicate = [NSPredicate predicateWithFormat:#"contactDepartment contains[cd] %#", self.searchField.text];
[...]
NSArray *predicatesArray = [NSArray arrayWithObjects:contactNamePredicate, contactDepartmentPredicate, contactEmailPredicate, contactPhonePredicate, linkPredicate, titlePredicate, nil];
NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicatesArray];
self.posts = [[Post findAllWithPredicate:predicate] mutableCopy];
if (self.posts.count != 0) {
self.noResultsLabel.hidden = YES;
for (int i = 0; i < self.posts.count; i++) {
Post * post = [self.posts objectAtIndex:i];
if (post.calendarEvent == YES) {
[self.events addObject:post];
} else {
[self.news addObject:post];
}
}
}
// reload the table view
[self.tableView reloadData];
[self performRemoteSearch];
}
}
- (void)search
{
[self setUpSearch];
[self hideKeyboard];
[self performRemoteSearch];
}
- (void)performRemoteSearch
{
// Should load the objects from JSON
// Note that the searchPath can vary depending on search text.
NSString *searchPath = [NSString stringWithFormat:#"/package_components/1/search.json?search=%#", self.searchField.text];
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager loadObjectsAtResourcePath:searchPath delegate:self];
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects
{
// This never gets called.
// Should update my arrays and then update the tableview, but it never gets called.
// Instead I get Error Domain=org.restkit.RestKit.ErrorDomain Code=1001 "Could not find an object mapping for keyPath: ''
}
Any tips on how i should or could do would be greatly appreciated.
I haven't used Managed Objects before but the first thing to do here is to activate the restkit log over object mapping and network request so you can check what is restkit getting from the server and how the mapping is working.
//This can be added in your app delegate
RKLogConfigureByName("RestKit/Network", RKLogLevelDebug);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
In second place, according to your JSON and that your search path changes, I think is better to use mapping for key path instead of resource path pattern. So you should try to map by key, like in this example:
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
[articleMapping mapKeyPath:#"title" toAttribute:#"title"];
[articleMapping mapKeyPath:#"body" toAttribute:#"body"];
[articleMapping mapKeyPath:#"author" toAttribute:#"author"];
[articleMapping mapKeyPath:#"publication_date" toAttribute:#"publicationDate"];
[[RKObjectManager sharedManager].mappingProvider setMapping:articleMapping forKeyPath:#"articles"];
And then load your data like:
- (void)loadArticles {
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/articles" delegate:self];
}
The other way to do this is to map by object, so RestKit detects the kind of object and performs the mapping and you make the request to any path.
If you have any question please leave a comment and I can improve my answer as needed.
I've never tried answering a question with a bounty before, let me try to give a useful answer from some recent work =)
1. how should my mapping look like
From your code, everything looks pretty fine. Are there any nesting of objects? Do you need to serialize for posting back to the server?
2. the json returns an array with two different kinds of objects.
Are your attributes the same (i.e. Event has a title, event has a date) with no surprises? If not, you have to use dynamic nesting.
If a resource path (i.e. your search path) receives a collection with different objects (your case), you have to use dynamic object mapping to load the objects.
Since you can edit the JSON structure, things can be simpler by leveraging on RestKit.
- Make sure the JSON has a root_key_path for the two different type of objects.
From an old experiment and some googling, RestKit can properly map a json output with different objects if they have proper rootKeyPaths. Resulting JSON should have a rough structure like:
{
"news" : [
{
"id" : 1,
"title" : "Mohawk guy quits"
},
{
"id" : 2,
"title" : "Obama gets mohawk"
}
],
"events" : [
{
"id" : 1,
"name" : "testing"
},
{
"id" : 2,
"name" : "testing again"
}
]
}
I cannot be sure 100% the above is correct. You can experiment by making your API return news only, if it works, then adding the events data into the mix.
- Load the objects from server
// Make a NS dictionary and use stringByAppendingQueryParameters
NSDictionary *searchParams = [NSDictionary dictionaryWithKeysAndObjects:
#"query",#"myQuery",
#"location",#"1.394168,103.895473",
nil];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/path/to/resource.json" stringByAppendingQueryParameters:searchParams] delegate:objectLoaderDelegate];
- Handle the "real" searching in your objectLoader Delegate
If it worked, the objects should be mapped to your Coredata entities. You can perform a local search using the NSPredicate method you posted above.
I prefer the design pattern where RestKit uses loadObjects... to get data from the server and maps it, the rest of the processing is done locally. This decoupling makes things more "app-like". You can do other form of manipulation using NSPredicates.
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
// Do some processing here on the array of returned objects or cede control to a method that
// you've built for the search, like the above method.
}
One example, if the search use case is restaurants nearby, it will probably make sense to load all the restaurants within the current lat/lon, and then perform the local filtering by name using Coredata. Your server will heart you.
Let me know and I'll try to improve the answer further.

Can't parse JSON from data in connectionDidFinishLoading – Maybe it's too much data?

I'm sending out a request to an external API and parsing a response with the SBJson parser. However, I suspect the response is so long, it is somehow getting jumbled.
In my mainviewcontroller.h file I set NSMutableData *receivedData; so that I can use it in the connection methods in the mainviewcontroller.m file.
However, after the connection finishes loading, I execute the following:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *dataString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
NSArray *allData = [dataString JSONValue];
}
However, I get a bunch of errors saying that the JSON is not properly formatted. So, when I look at the JSON, its very long – but here and there, there are problems... for example, the "updated_at" below.
{
"id": 7844333,
"position": 3,
"content": "Cell height is off by 5 pixels",
"created_at": "2012-06-04T20:31:30-05:00",
"updated_at": "2ator": {
"id": 98258,
"name": "Brian"
}
What I think happened above is that updated at has a value of "2012-06...etc" and the next key-value item would be creator : { id, name } but it somehow got jumbled into updated at.
Anyone having a similar problem? I don't think the problem is with the JSONValue because I nslog out the dataString before it gets parsed, and thats where I find the JSON errors.
What I mean by that is that NSString *dataString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding]; is just a long string, but has bad JSON in it because it is jumbled.
Are you using receivedData by more than one connection at once?
:)
I think your json is wrong. For checking that just put json file into the :
http://jsonlint.com/
If it is valid then:
Import the SBJSON framework classes into your project.And try the following code:
SBJSON *parser=[[SBJSON alloc]init];
NSDictionary * dictionary = [parser objectWithString:responseString];
this will give you data into dictionary then by using:
NSString *firstParseData=[dictionary objectForKey:#"your key"];
you can retrieve the data. Hope this will work in your case.

Objective - C : How to read json? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to parse JSON in Objective-C?
I totally new with Objective c, I need to value of distance from google distance matrix json, but data that I can get from json just the first element,
so How to get value in distance this json?
{
"destination_addresses": [
"San Francisco, Californie, États-Unis",
"Victoria, BC, Canada"
],
"origin_addresses": [
"Vancouver, BC, Canada",
"Seattle, État de Washington, États-Unis"
],
"rows": [
{
"elements": [
{
"distance": {
"text": "1 732 km",
"value": 1732128
},
"duration": {
"text": "3 jours 23 heures",
"value": 340902
},
"status": "OK"
}
]
}
],
"status": "OK"
}
ok here is my sample code that just follow from JSON Tutorial :
I already use SBJSON , but I can only rows key, so how to get value in Distance key?
SBJSON *parser = [[SBJSON alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL
URLWithString:#"http://maps.googleapis.com/maps/api/distancematrix/json?origins=Vancouver+BC|Seattle&destinations=San+Francisco|Victoria+BC&mode=bicycling&language=fr-FR&sensor=false"]];
// Perform request and get JSON back as a NSData object
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSArray *statuses = [parser objectWithString:json_string error:nil];
for (NSDictionary *status in statuses)
{
NSLog(#"%# - %#", [status objectForKey:#"rows"]);
}
If you're targeting iOS 5 or OS X v10.7 then you can just use Apple's own NSJSONSerialization. You probably particularly want +JSONObjectWithData:options:error:. Using the built-in stuff is always preferable to third-party options because it eliminates the problem that the third party has no direct interest in fixing its bugs and no way to supply fixes for your application without your intervention.
If you want to support older devices then you probably want to import SBJSON — as as Rengers suggests — or any of the hundred others. For the reasons above I highly recommend you fall back on those only if NSJSONSerialization isn't available.
EDIT: it appears you already have SBJSON in place and are asking about how to traverse the results? From reading the JSON:
the root object will be a dictionary
within it, rows will be an array of dictionaries
each dictionary has an array named elements
each entry in that array is a dictionary
within that, distance is another dictionary
distance contains two strings, with keys "text" and "value"
So if you weren't to do any validation at all, to get to the distance dictionary you might do:
NSDictionary *result = [parser objectWithString:...];
NSLog(#"distance dictionary is: %#"
[[[[[
result objectForKey:#"rows"]
objectAtIndex:0]
objectForKey:#"elements"]
objectAtIndex:0]
objectForKey:#"distance"]
);
Based on your sample code, it looks like you may actually be getting an array of the sorts of dictionary posted rather than the dictionary directly. Obviously adapt if that's the case.
You need to use a JSON parser like SBJSON. This will create a NSDictionary/NSArray with all the values and keys. Then use the normal dictionary and array methods to access the data.

Have TouchJSON return mutable objects?

I am receiving some json from a web service. I parse this using the TouchJSON library.
I keep the data around for the user to change certain values and then I want to return it to the web service.
The JSON object I get contains NSDictionary Objects within the object, like this:
[
{
"id": null,
"created_at": 12332343,
"object": {
"name": "Some name",
"age" : 30
},
"scope": "it stuff",
"kind_id": 1,
"valid": true,
"refering_statement": {
"id": 95
},
"user": {
"id": 1
}
}
]
If I want to change values in this dictionary, I can't because the returned objects from TouchJSON are not mutable.
Is there a way to have have TouchJSON return mutable objects?
or is there a way to have Objective C make an NSDictionary and all its children mutable?
or do I have to just go through every NSDictionary in NSDictionary and copy all data into a mutable copy and then reinsert everything?
Hope someone can help me out on this one:) thanks in advance.
TouchJson has an option to return mutable object rather than normal. (I found it by looking at source code.) Default is to return its "copy", not "mutablecopy".
NSError *error = nil;
jsonString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF32BigEndianStringEncoding];
CJSONDeserializer *jsondeserializer = [CJSONDeserializer deserializer];
jsondeserializer.scanner.options = kJSONScannerOptions_MutableContainers;
NSMutableDictionary *jsonitems = [[NSMutableDictionary alloc] initWithDictionary:[jsondeserializer deserializeAsDictionary:jsonData error:&error]];
You can create a mutable dictionary from a dictionary like this.
(assuming your json parsed dictionary was named jsonDictionary)
NSMutableDictionary *userDictionary = [NSMutableDictionary dictionaryWithDictionary:jsonDictionary];
Hope that solves it for you.