I am very new with ios dev and I have been struggling with almost everything so far. It's really very frustrating really.
I have put the comment /code crashes here/ to indicate where my app flow stops. I am requesting the json data with
NSData* data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://192.168.2.4/iRestaurant/users"]];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
The implementation for fetchedData
- (void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
//NSLog(#"%#", json);
NSArray* latestLoans = [json objectForKey:#"Users"]; //2
NSEnumerator* enumerator = [latestLoans objectEnumerator];
id element;
while(element = [enumerator nextObject]) {
Result *fetchedResults = [Result init];
/*crashes here*/
fetchedResults.name = [[element objectForKey:#"User"] objectForKey:#"name"];
fetchedResults.email = [[element objectForKey:#"User"] objectForKey:#"name"];
[results addObject:fetchedResults];objectForKey:#"name"]);
}
}
Result is a custom object and is a subclass of NSObject. I have imported Result.h. I have also declared NSMutableArray *results in my current file's declaration file. I am pushing the objects into results. The header file for Result looks like
#interface Result : NSObject
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSString *email;
#property (nonatomic, assign) int ID;
#end
I will be implementing cellForRowAtIndexPath and pushing the contents of results into the reusable cell.
My JSON response is
{"Users":
[{"User":
{"id":"1",
"name":"Kishor kundan",
"email":"kis#kun.ca",
"password":"asdfasdf",
"fb_id":"1234444"
}
},
{"User":
{"id":"2",
"name":"adsfasdf",
"email":"asdfasdf#asdf.asdf",
"password":"asdfasdf",
"fb_id":"123123"
}
}]
}
The code was crashing due to object not found. My code was referencing a location that was not there. I took #btype's suggestion from my other question
Related
A lot of modern programming languages have JSON libraries that support encoding and decoding json to/from "plain old objects" - i.e. instances of classes that primarily just have data properties (properties can either be types that can be trivially de/encoded or other plain old objects). Examples include Google's GSON, golang's encoding/json and others.
Is there something similar to Objective-C?
I know that it is possible to enumerate properties for Objective-C classes, and it seems reasonable that someone would have used that capability to create a JSON "bean mapper", but Google searching yielded no results for me, except this blog post on Apple's Swift website showing how to manually deserialize JSON to "model objects" and why they think that doing this automatically (DRYing the code) is a bad idea (*).
*) The reasoning is basically, that not needing to write a lot of boilerplate (their sample implementation is 36 LoC to parse 3 properties) is not a significant improvement and building a couple of optional callbacks to allow data validation is hard. I obviously disagree with all of this.
Here is my solution, which is not based on a library - as I couldn't find any - but instead using the Foundation and Objective-C runtime methods - as discussed in the comments above:
#import <objc/runtime.h>
NSArray<NSString*>* classPropertyList(id instance) {
NSMutableArray* propList = [NSMutableArray array];
unsigned int numProps = 0;
objc_property_t* props = class_copyPropertyList(object_getClass(instance), &numProps);
for (int i = 0; i < numProps; i++)
[propList addObject:[NSString stringWithUTF8String:property_getName(props[i])]];
free(props);
return propList;
}
NSString* typeOfProperty(Class clazz, NSString* propertyName) {
objc_property_t prop = class_getProperty(clazz, [propertyName UTF8String]);
NSArray<NSString*>* propAttrs = [[NSString stringWithUTF8String:property_getAttributes(prop)] componentsSeparatedByString:#","];
if ([(propAttrs[0]) hasPrefix:#"T#\""])
return [propAttrs[0] componentsSeparatedByString:#"\""][1];
return nil;
}
#implementation JSONMarshallable
- (NSData*)toJSON {
return [self toJSON:self withNullValues:YES];
}
- (NSString*)toJSONString {
return [self toJSONString:self withNullValues:YES];
}
- (NSData*)toJSON:_ withNullValues:(bool)nullables {
NSError* error;
NSDictionary* dic = [self toDictionary:self withNullValues:nullables];
NSData* json = [NSJSONSerialization dataWithJSONObject:dic options:0 error:&error];
if (!json) {
NSLog(#"Error encoding DeviceConfigurationRequest: %#", error);
return nil;
}
return json;
}
- (NSString*) toJSONString:_ withNullValues:(bool)nullables {
NSData* json = [self toJSON:self withNullValues:nullables];
return [[NSString alloc] initWithBytes:[json bytes] length:[json length] encoding:NSUTF8StringEncoding];
}
- (NSDictionary*)toDictionary:_ withNullValues:(bool)nullables {
NSMutableDictionary* dic = [NSMutableDictionary new];
for (id propName in classPropertyList(self)) {
id val = [self valueForKey:propName];
if (!nullables && (val == nil || val == NSNull.null))
continue;
if ([val respondsToSelector:#selector(toDictionary:withNullValues:)])
val = [val toDictionary:val withNullValues:nullables];
[dic setObject:(val == nil ? NSNull.null : val) forKey:propName];
}
return dic;
}
- (instancetype)initWithJSONString:(NSString*)json {
return [self initWithJSON:[json dataUsingEncoding:NSUTF8StringEncoding]];
}
- (instancetype)initWithJSON:(NSData*)json {
NSError* error;
if (json == nil)
return nil;
NSDictionary* dataValues = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error];
if (!dataValues) {
NSLog(#"Error parsing invalid JSON for %#: %#", NSStringFromClass(object_getClass(self)), error);
return nil;
}
return [self initWithDictionary:dataValues];
}
- (instancetype)initWithDictionary:(NSDictionary*)dataValues {
if (dataValues == nil)
return nil;
if (self = [super init])
for (id key in dataValues) {
id val = [dataValues objectForKey:key];
if (![self respondsToSelector:NSSelectorFromString(key)])
continue;
NSString* typeName = typeOfProperty([self class], key);
if ([val isKindOfClass:[NSNull class]]) { // translate NSNull values to something useful, if we can
if (typeName == nil)
continue; // don't try to set nil to non-pointer fields
val = nil;
} else if ([val isKindOfClass:[NSDictionary class]] && typeName != nil)
val = [[NSClassFromString(typeName) alloc] initWithDictionary:val];
[self setValue:val forKey:key];
}
return self;
}
#end
It is then easy to create custom model objects by inheriting from JSONMarshallable, like so:
model.h:
#import "JSONMarshallable.h"
#interface MyModel : JSONMarshallable
#property NSString* stringValue;
#property NSNumber* numericValue;
#property bool boolValue;
#end
model.m:
#implementation MyModel
#end
SomeThingElse.m:
// ...
NSData* someJson;
MyModel* obj = [[MyModel alloc] initWithJSON:someJson];
NSString* jsonObj = [obj toJSONString:nil withNullValues:NO];
Critics are welcome! (I'm not very good at Objective C and probably made a lot of faux pas ðŸ¤)
Issues:
I can handle nullable numbers with NSNumber* (though C primitives work fine for non-nullable numbers), but I don't know how to represent nullable booleans - i.e. a field that is optional and not encoded when using withNullValues:NO.
Sending fields for which there are no properties (for example, the server I work with sends values in both snake-case and underscrore-case to make it easy to parse) throws exception. (solved by using respondsToSelector: and setValue: instead of setValuesForKeysWithDictionary:).
Trying to set nil values to primitive-typed fields causes exceptions. (solved by checking for property type and NSNull).
Doesn't work at all for nesting objects - i.e. a custom model object with properties that are also custom model objects. (solved by checking for property types and recursing encoding/decoding).
Probably doesn't handle arrays well - I have yet to need those in my software, so I haven't implemented proper support (though I verified that encoding simple string arrays works well).
I have a MySQL database and a PHP script that work perfectly. However, I was wondering how I could get this data from JSON format to a UITextView. I also have a table being populated with 2 other JSON elements. Thanks!
TableInfo.h
#import <Foundation/Foundation.h>
#interface TableInfo : NSObject
#property (nonatomic, strong) NSString *Title;
#property (nonatomic, strong) NSString *SubTitle;
#property (nonatomic, strong) NSString *Description;
#end
HomeModel.h
#import <Foundation/Foundation.h>
#protocol HomeModelProtocol <NSObject>
- (void)itemsDownloaded:(NSArray *)items;
#end
#interface HomeModel : NSObject <NSURLConnectionDataDelegate>
#property (nonatomic, weak) id<HomeModelProtocol> delegate;
- (void)downloadItems;
#end
HomeModel.m
#import "HomeModel.h"
#import "TableInfo.h"
#interface HomeModel()
{
NSMutableData *_downloadedData;
}
#end
#implementation HomeModel
- (void)downloadItems
{
// Download the json file
NSURL *jsonFileUrl = [NSURL URLWithString:#"http://gandouhaiti.ueuo.com/service.php"];
NSLog(#"Donwload the JSON file");
// Create the request
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:jsonFileUrl];
NSLog(#"Create the request");
// Create the NSURLConnection
[NSURLConnection connectionWithRequest:urlRequest delegate:self];
NSLog(#"Create the NSURLConnection");
}
#pragma mark NSURLConnectionDataProtocol Methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"Inititalize the data object");
// Initialize the data object
_downloadedData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the newly downloaded data
[_downloadedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Create an array to store the locations
NSMutableArray *_locations = [[NSMutableArray alloc] init];
// Parse the JSON that came in
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
// Loop through Json objects, create question objects and add them to our questions array
for (int i = 0; i < jsonArray.count; i++)
{
NSDictionary *jsonElement = jsonArray[i];
// Create a new location object and set its props to JsonElement properties
TableInfo *newdata = [[TableInfo alloc] init];
newdata.Title = jsonElement[#"Title"];
newdata.SubTitle = jsonElement[#"SubTitle"];
newdata.description = jsonElement[#"Description"];
// Add this question to the locations array
[_locations addObject:newdata];
}
// Ready to notify delegate that data is ready and pass back items
if (self.delegate)
{
[self.delegate itemsDownloaded:_locations];
}
}
#end
If you wan to display the JSON in UITextView in storyboard, kindly try the following:
#implementation ViewController
NSString * JSON;
-(void) viewDidLoad
{
JSON = //get data from remote;
NSString *stringForTextView = #"";
NSData *data = [JSON dataUsingEncoding:NSUTF8StringEncoding];
NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
if([array count] >0)
{
for(NSDictionary *d in array)
{
stringForTextView = [NSString stringWithFormat:#"Title:%#\nSubTitle:%#\nDescription:%#\n", [d valueForKey:#"Title"], [d valueForKey:#"SubTitle"], [d valueForKey:#"Description"]];
}
}
_textView.text = stringForTextView;
}
#end
Im having issues with this code reading some .json files, it works fine on json that can be read via IE like a webpage but wont work on files that the url is tring to download them like a ftp style.
In the end im looking at populating labels with the json info once this works but cant see what around the URLRequest is causing it.
This is what code im testing it with:
// WDViewController.h
#import <UIKit/UIKit.h>
#interface WDViewController : UIViewController{
NSArray *jsonArray;
NSMutableData *data;
}
#property (weak, nonatomic) IBOutlet UILabel *outputLabel;
-(IBAction)WDPlayerButtonUpdate:(id)sender;
#end
// WDViewController.m
#import "WDViewController.h"
#interface WDViewController ()
#end
#implementation WDViewController
#synthesize outputLabel;
-(IBAction)WDPlayerButtonUpdate:(id)sender{
NSURL *url = [NSURL URLWithString:#"http://gooruism.com/feed/json"];
NSURLRequest *data = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
data = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData{
[data appendData:theData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
jsonArray = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
// Loop through jsonArray
for (int i = 0; i< jsonArray.count; i++)
{
outputLabel.text = [[jsonArray objectAtIndex:i] objectForKey:#"889"]; // Set Labels
}
}
#end
The json file and url are just one that I found looking for a solution via Google and are only for testing.
After a lot more Google and learning some more about json,
Ive got the code working.
Ended up being more of a null exemption from empty indexes and not calling objectForKeys correctly, as was leaving strings that errored when then using to populate a label.
Code I finally got to work:
//
// WDViewController.m
// JSON Test
#import "WDViewController.h"
#interface WDViewController ()
#end
#implementation WDViewController
#synthesize testLabel;
-(IBAction)WDPlayerButtonUpdate:(id)sender
{
// Set network activity indicator
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
// Set json url.
NSURL *url = [NSURL URLWithString:#"<url for json>"];
// get the url page into the nsdata object.
NSData *jsonData = [NSData dataWithContentsOfURL:url];
// Read json (from jasondata) and convert to object.
if ( jsonData != nil)
{
NSError *error = nil;
id result = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if (error == nil)
NSLog (#"%#", result);
NSLog(#"Test 1");
// Retrieve array and show in log
NSLog(#"From status index: %#", [result objectForKey:#"status"]);
// Retrieve data and log
NSLog(#"From profile.handle index: %#", [[result objectForKey:#"profile"]objectForKey:#"handle"]);
NSLog(#"Next Test 2"); // Populate label with data handle
yestLabel.text = [[result objectForKey:#"profile"]objectForKey:#"handle"];
NSLog(#"End of Tests");
}
}
#end
I am new to xcode and I seem to be stuck at trying to create a tableview with sections.
My code below creates the header however I cannot seem to display the rows that belong to the section. I'm sure it has to do with the multidimensional array. I say this because "dobSection" value is "results" when I suppose it should display the dates under "DOB".
I have spent hours trying many different ways to get this to work..... eh! No luck.
The Json is somewhat like this:
{"results":
[{"name":"joe","surname":"blow","DOB":"1990-01-01"},
{"name":"jane","surname":"doe","DOB":"1990-01-01"},
{"name":"john","surname":"doe","DOB":"1995-06-06"}]
}
and the code
NSMutableArray *jsonResults;
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
jsonResults = [json objectForKey:#"results"];
self.dob = json;
self.keys = [jsonResults valueForKey:#"DOB"];
[self.tableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [keys count];
//return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
// NSString *key = [keys objectAtIndex:section];
NSString *key = [keys objectAtIndex:section];
NSArray *dobSection = [dob objectForKey:key];
return [dobSection count];
}
in addition by replacing this:
NSArray *dobSection = [dob objectForKey:key];
with
NSArray *dobSection=[[games objectForKey:#"results"]valueForKey:#"dob"];
I get all rows.
I cannot get them to group by DOB
My code above may be flawed however I would love some input on how to get this right.
Thank you
This is my string:
[{"id":"1","nome":"Adriatik"},{"id":"2","nome":"Ard"},{"id":"3","nome":"Albana"},{"id":"4","nome":"Adriana"}]
I would like to parse all 'name' of the JSON string into a NSMutableArray.
Sorry for my english!
Whenever I have to handle some JSON code, the first thing I like to do is create a class based on the JSON text. So, for example if your JSON is representing a U.S. state, create a "State" class.
There's a cool little product that you can use for this. It's called Objectify and costs about $15. No doubt people can advise on other free stuff that might do something similar.
For the actual Json parsing, I use SBJson. There's quite a few Json parsing frameworks out there for Objective-C so definitely have a look around to see what takes your fancy.
Next, with SBJson, do the actual parsing:
-(NSDictionary *)parseJsonFromUrl
{
NSAssert(mUrl, #"Must set a url before invoking %#", __PRETTY_FUNCTION__);
// Create new SBJSON parser object
SBJsonParser *parser = [[SBJsonParser alloc] init];
// Prepare URL request to download JSON
NSURLRequest *request = [NSURLRequest requestWithURL:mUrl];
// Perform request and get JSON back as a NSData object
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
// Get JSON as a NSString from NSData response
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
// parse the JSON response into an object
// Here we're using NSArray since we're parsing an array of JSON status objects
return [parser objectWithString:json_string error:nil];
}
That returns a NSDictionary. You know have to look through that dictionary to set the values of your model class. Here's how to do that whilst at the same time loading the values into the NSMutableArray:
-(void)downloadJsonData
{
NSDictionary *statesDict = [self parseJsonFromUrl];
NSMutableArray *statesArray = [NSMutableArray arrayWithCapacity:[statesDict count]];
for (NSDictionary *stateDict in stateDict)
{
State *aState = [[[State alloc] init] autorelease];
aState.stateId = [stateDict valueForKey:#"id"];
aState.name = [stateDict valueForKey:#"name"];
[statesArray addObject:aState];
}
}
Note that I use a property name of stateId not id so as not to clash with the Objective-C object pointer type.
Use SBJson classes and call -JSONValue method
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
// NSLog(#" Response String %#", responseString);
//converted response json string to a simple NSdictionary
NSMutableArray *results = [responseString JSONValue];