How to parse json specific fields using objective-c - objective-c

I'm trying to parse a very simple json object with 1 value. (ocUnit test below)
- (void) testHatCartParseWithValidRefId {
NSString* data = #"{\"refid\":999}";
Cart* obj = [HatCartParseJson parseJsonAndReturnObject:data];
STAssertTrue([obj.refid isEqualToString:#"999"], #"fail");
}
In the implementation everything fails when I add the line to pull it from either key or index. How should I pull this from the json input? Please keep in mind I need this json parse (not string) the actual code I'm working with is a large set of JSON data.
+ (Cart *) parseJsonAndReturnObject:(NSString *)json
{
NSArray* cart = [json JSONValue];
for (NSDictionary* item in cart) {
Cart* obj = [[Cart alloc] init];
//NSString* refid = [item objectAtIndex:0];
//NSString* refid = [item objectForKey:#"refid"];
[obj setRefid:#"999"];
return obj;
}
return nil;
}
Thank you in advance

You are expecting that the return value of JSONValue is an NSArray, which in this case it isn't.
So, you must do a check if the return value is actually an NSArray, and if it is, then iterate through the collection, otherwise check if it's an NSDictionary, and if it is, then return the Cart object with the refid from the NSDictionary. If all of this fails, then just return nil.
As a side point, according to Apple's Object Ownership Policy, you should return autorelease-d objects from methods whose names do not contain the words "alloc", "new" or "copy". This would be one such method where you'd return an autorelease-d object.
+ (Cart *) parseJsonAndReturnObject:(NSString *)json
{
id cart = [json JSONValue];
NSString* refid = nil;
if([cart isKindOfClass:[NSArray class]]) {
refid = [[cart objectAtIndex:0] objectForKey:#"refid"];
} else if([cart isKindOfClass:[NSDictionary class]]) {
refid = [cart objectForKey:#"refid"];
}
if(refid) {
Cart* c = [[Cart alloc] init];
[c setRefid:refid];
return [c autorelease];
}
return nil;
}

Related

Using method parameters check to see if array object is equal to another string (objective-C)?

I'm all self taught so please keep the techincal jargin to a minimum. Theoretically if I had a method and it had a parameter that is the name of an array. How do I check to see if that array index 5 is equal to #"Yes" or #"No". I know it's one of these because its testing to see if the picture is appearing in the veiw controller. Here is an example:
-(void)methodName :(NSMutableArray *)arrayNameInMethod {
if ( [NSMutableArray *(arrayNameInMethod) indexOfObject:5] == #"Yes"){
//Hide a different picture assocciated with the Array
} else {
//Unhide a different picture assocciated with the Array
};
Also how do you do use the parameter "arrayNameInMethod" to replace the object. Basically:
if(Picture Clicked and picture is Unhidden) {
[NSMutableArray *(differentArrayNameInMethod) replaceObjectAtIndex:5 withObject: #"True)
};
(this is all in another method)
Comment #2: You can't use the parameters the same way because it's a string. You can't access an array with a name as a string.
Thank you so much!
I think what you will need is a dictionary, mapping the name to the array. I will give a barebones implementation:
#interface YourClass()
{
NSMutableDictionary *_arrayMap;
}
#end
#interface YourClass
- (instancetype)init
{
self = [super init];
if (self) {
// You might do this somewhere else, like viewDidLoad:
_arrayMap = [NSMutableDictionary new];
}
return self;
}
- (void)someMethod
{
// Some functionality to add a new array:
// Note the array contains NSNumber and NSString objects:
_arrayMap[#"sampleName"] = [#[ #(0), #"one", #(2.0), #"three", #(4), #(YES)] mutableCopy];
}
-(BOOL)checkForConditionInArrayNamed:(NSString *)arrayName
{
BOOL retval = NO;
NSMutableArray *array = _arrayMap[arrayName];
if ([array count] > 5) {
id obj = array[5];
if ([obj isKindOfClass:[NSNumber class]])
retval = [obj boolValue];
}
return retval
};
#end
You then call checkForConditionInArrayNamed: to check for the condition of the named array and act accordingly.

ios parse jsonresult how to iterate if it's not a dictionary?

I have this issue when I'm trying to parse my jsonresult that comes from a webapi. Take note, that whenver it would return a json result that looks like this:
[{"Id":0, "Name":Wombat, "Category":Animal},
{"Id":1, "Name":Trident, "Category":Object}]
This code works on that result:
NSArray *jsonresult = [WCFServiceRequest processWebApiGETRequestWithURL:url];
if(jsonresult){
for(id item in jsonresult){
NSLog(#"item is %#", item);
if([item isKindOfClass:[NSDictionary class]]){
object.Id = [item objectForKey:#"Id"];
object.name = [item objectForKey:#"Name"];
object.category = [item objectForKey:#"Category"];
}
}
}
But once it returns a none list result that looks like so:
{"Id":1, "Name":Trident, "Category":Object}
It would not go through the
if([item isKindOfClass:[NSDictionary class]]){
Now if I take it out and just let it go straight to the assignment of the properties. The "item" variable that is returned from the jsonarray is the key eg: Id, Name, etc. Now I'm not sure how to properly iterate through the thing and assign it using keys. It seems like it's using indexes? Do I make another dictionary?
Its quite simple to get the solution of it, when you are unaware about the result DataType simply TypeCast your object to id (A Generic DataType)
Store your json Response in id
id jsonresult = [WCFServiceRequest processWebApiGETRequestWithURL:url];
// Check whether Response is An Array of Dictionary
if([jsonresult isKindOfClass:[NSArray class]])
{
NSLog(#"it has multiple Dictionary so iterate through list");
if(jsonresult){
for(id item in jsonresult){
NSLog(#"item is %#", item);
if([item isKindOfClass:[NSDictionary class]]){
object.Id = jsonresult[#"Id"];
object.name = jsonresult[#"Name"];
object.category = jsonresult[#"Category"];
}
}
}
}
// Its Dictionary
else if([jsonresult isKindOfClass:[NSDictionary class]])
{
NSLog(#"It has only one dictionary so simply read it");
object.Id = jsonresult[#"Id"];
object.name = jsonresult[#"Name"];
object.category = jsonresult[#"Category"];
}
Your response is Dictionary when you have only 1 Record in Result, and when you have more then 1 Record it will be an Array.

Objective-C Creating Dynamic Objects

After parsing a XML File,i want to create dynamic objects like textbox and buttons based on the contents of xml.
I have parsed my xml and retrieved the data.
I have created a seperate class for XMLParser and made the call in my vieDidload method.
Is it possible to call a method in my mainViewController class from my XMLParser class to create and the dynamic objects.
You can create your own class which will read the parsed value and based on that you go on to create all the GUI Objects.That is not very difficult. Even you can add binding, connections, constraints etc.
I actually did same thing while creating a framework for my project. I cant post the code as its copyright but I can share some of the hits. One basic hint I have mentioned.
well... you can do it with a NSMutableDictionary.
OR you can fill objects that you modeled before from xml
basically just alloc init the existing object and set its properties:
example:
//parse xml
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path]];
parser.delegate = (id<NSXMLParserDelegate>)self;
[parser parse];
e.g. company objects
//callback from parser
- (void)didStartFirmaWithParser:(NSXMLParser*)parser andAttributes:(NSDictionary*)dict {
[[self __didStartEntity:#"RLStoreFirma" withParser:parser] didStartFirmaWithParser:parser andAttributes:dict];
}
//general callback, sets the parsers delegate to THIS new object which then gets filled
- (id)__didStartEntity:(NSString*)name withParser:(AQXMLParser*)parser {
NSEntityDescription *entity = [[store.managedObjectModel entitiesByName] objectForKey:name];
M42StoreEntry *entry = (id)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:store.managedObjectContext];
entry.store = self->store;
parser.delegate = entry;
return entry;
}
the object that is to be filled
#import "RLStoreFirma(info).h"
#implementation RLStoreFirma (info)
- (void)didStartFirmaWithParser:(AQXMLParser*)parser andAttributes:(NSDictionary*)dict {
}
- (void)didEndFfkbWithParser:(AQXMLParser*)parser {
self.fkb = currentXMLCharacters;
}
- (void)didEndFfirWithParser:(AQXMLParser*)parser {
self.name = currentXMLCharacters;
}
- (void)didEndFirmaWithParser:(AQXMLParser*)parser {
if(!self.name) {
self.name = self.fkb;
}
[self didEndMainWithParser:(AQXMLParser*)parser];
}
generating new objects at runtime isnt allowed with the iphone sdk / its objc runtime
#pragma mark parser magic
- (SEL) __startSelectorForElement: (NSString *) element
{
NSString * str = nil;
NSMutableString * eSel = [NSMutableString stringWithString: [[element substringWithRange: NSMakeRange(0,1)] uppercaseString]];
if ( [element length] > 1 )
{
[eSel appendString: [element substringFromIndex: 1]];
NSRange range = [eSel rangeOfString: #"-"];
for ( ; range.location != NSNotFound; range = [eSel rangeOfString: #"-"] )
{
NSString * cap = [[eSel substringWithRange: NSMakeRange(range.location+1, 1)] uppercaseString];
range.length += 1;
[eSel replaceCharactersInRange: range withString: cap];
}
}
str = [NSString stringWithFormat: #"didStart%#WithParser:andAttributes:", eSel];
return ( NSSelectorFromString(str) );
}
syntactic sugar to make NSParser nicer.. I cant give you ALL code but I think this might help - i hope

NSdictionary returns error when 1 returns

Everything works when i get more then 1 objects back but when its only 1 it reacts weird, i can't find the solution for it.
First i set everything in an array:
NSArray *array = [[[dictionary objectForKey:#"Response"] objectForKey:#"objecten"] objectForKey:#"object"];
if (array == nil) {
NSLog(#"Expected 'results' array");
return;
}
then i use a for loop on a dictionary
for (NSDictionary *resultDict in array) {
SearchResult *searchResult;
NSString *wrapperType = [resultDict objectForKey:#"type"];
if ([wrapperType isEqualToString:#"rent"])
{
searchResult = [self parseHuur:resultDict];
}
if (searchResult != nil) {
[searchResults addObject:searchResult];
}}
So when results get back more then 1 everything works great, but when just one gets back i get:
-[__NSCFString objectForKey:]: unrecognized selector sent to instance 0x6e52c30
*** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSCFString objectForKey:]:
unrecognized selector sent to instance 0x6e52c30'
it points to this line:
NSString *wrapperType = [resultDict objectForKey:#"type"];
I really don't get it...
i check the results of the api in the browser with the same link and it really returns 1 object, but when i log the resultDict (NSlog it) i get only one answer: id instead of the whole object with all parameters (i don't know if this is the right name for it)
how can that be ?
Some of your results aren't full NSDictionaries but rather just NSStrings. You can check for this:
for (id result in array) {
if ([result isKindOfClass:[NSDictionary class]]) {
NSDictionary *resultDict = (NSDictionary *)result;
...
As per your comments, array is not always an array as you have mentioned. It could be an array or dictionary. So try this,
id someObject = [[[dictionary objectForKey:#"Response"] objectForKey:#"objecten"] objectForKey:#"object"]; //naming it as someObject since it is not always an array
if (someObject == nil) {
NSLog(#"Expected 'results' array");
return;
}
if ([someObject isKindOfClass:[NSDictionary class]]) { //Just add this
someObject = [NSArray arrayWithObject:someObject];
}
NSArray *array = (NSArray *)someObject;//type cast to an array now
for (NSDictionary *resultDict in array) {
SearchResult *searchResult;
NSString *wrapperType = [resultDict objectForKey:#"type"];
if ([wrapperType isEqualToString:#"rent"])
{
searchResult = [self parseHuur:resultDict];
}
if (searchResult != nil) {
[searchResults addObject:searchResult];
}
}
When you use the fast enumeration for a NSDictionary, the iterating variable is from the set of keys in the dictionary not the values.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocFastEnumeration.html
The resultDict isn't an NSDictionary hence you can't invoke objectForKey: on this object.
A better solution would be to treat resultDict as id type in the for loop, and check its class type for NSDictionary before using it.
-[__NSCFString objectForKey:]
So it's calling the objectForKey: method on an NSString. It seems that the API you're using for getting the objects follows a common idiom: it uses duck-typing/polimorphism (to use these nice OO-related words) and it returns either an array of objects if it has more than results, or a single object (and not an array of one element) when it has only one result. So, you have to use reflection (OMG, even more OO terminology!) to inspect whether the returned object is actually an array - either
id result = [dictionary objectForKey:#"Response"];
id value;
if ([result isKindOfClass:[NSDictionary class]]) {
value = [[result object objectForKey:#"objecten"] objectForKey:#"object"];
} else {
value = result;
}
or
id value;
if ([dictionary isKindOfClass:[NSDictionary class]]) {
value = [[[dictionary objectForKey:#"Result"] result object objectForKey:#"objecten"] objectForKey:#"object"];
} else {
value = dictionary;
}
Try both, whichever works should be fine.

dynamic typecasting of managed object properties when doing setValuesForKeysWithDictionary

I have a few NSManagedObject classes. I am pulling down some JSON data from a server which I parse into an NSDictionary object. When the conversion from JSON to NSDictionary occurs, all of my data is cast as NSStrings. When I then map this dictionary to my managedObject I get this:
Unacceptable type of value for attribute: property = "idexpert"; desired type = NSNumber; given type = __NSCFString; value = 1.'
So my managedobject is looking for an NSNumber but it's getting a string and throwing an exception
Is there a way that when I call setValuesForKeysWithDictionary I can automagically cast the values properly for the managedobject they are going into?
Thanks!
The best way to manage JSON attributes while saving in core data is to write a generic function that can override setValuesForKeysWithDictionary as per below:
#implementation NSManagedObject (safeSetValuesKeysWithDictionary)
- (void)safeSetValuesForKeysWithDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter
{
NSDictionary *attributes = [[self entity] attributesByName];
for (NSString *attribute in attributes) {
id value = [keyedValues objectForKey:attribute];
if (value == nil) {
continue;
}
NSAttributeType attributeType = [[attributes objectForKey:attribute] attributeType];
if ((attributeType == NSStringAttributeType) && ([value isKindOfClass:[NSNumber class]])) {
value = [value stringValue];
} else if (((attributeType == NSInteger16AttributeType) || (attributeType == NSInteger32AttributeType) || (attributeType == NSInteger64AttributeType) || (attributeType == NSBooleanAttributeType)) && ([value isKindOfClass:[NSString class]])) {
value = [NSNumber numberWithInteger:[value integerValue]];
} else if ((attributeType == NSFloatAttributeType) && ([value isKindOfClass:[NSString class]])) {
value = [NSNumber numberWithDouble:[value doubleValue]];
} else if ((attributeType == NSDateAttributeType) && ([value isKindOfClass:[NSString class]]) && (dateFormatter != nil)) {
value = [dateFormatter dateFromString:value];
}
[self setValue:value forKey:attribute];
}
}
#end
For more details refer this link here: http://www.cimgf.com/2011/06/02/saving-json-to-core-data/
If the json you are receiving actually has number values and they are getting cast as string you should get a new json parser. I recommend NXJson. Otherwise there wont be any magical casting happening.
If the json is returning strings such as {"idexpert":"1"} then you can override setValuesForKeysWithDictionary and do something like the code below;
-(void)setValuesForKeysWithDictionary:(NSDictionary *)d{
NSMutableDictionary *newDict = [NSMutableDictionary dictionaryWithDictionary:d];
NSString *value = [newDict valueForKey:#"idexpert"];
[newDict setValue:[NSNumber numberWithLong:[value longValue]] forKey:#"idexpert"];
[super setValuesForKeysWithDictionary:newDict];
}