SBJsonParser can't parse NSArray - objective-c

I am trying to parse NSArray to JSON but I get the following error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM
JSONRepresentation]: unrecognized selector sent to instance 0xa93e460'
* First throw call stack: (0x21f1012 0x1feae7e 0x227c4bd 0x21e0bbc 0x21e094e 0x3445a 0x33ecc 0x26a453f 0x26b6014 0x26a72e8 0x26a7450
0x95e22e12 0x95e0acca) libc++abi.dylib: terminate called throwing an
exception
I have included all classes from SBJson_3.1.1/Classes directory.
This is code:
NSMutableArray* arr = ...get array
NSString* jsonArr = [arr JSONRepresentation]; // here I get error
When I do this in array of simple strings it works:
NSData *jsonData = [NSJSONSerialization arr
options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
But my array contain list of objects (Person) maybe there is a problem.
I use Item instead of person just as example
Item.h
#interface Item : NSObject
{
BOOL IsOpen;
NSString* Description;
}
#property int ItemId;
#property int SequenceId;
#property BOOL IsOpen;
#property NSString* Description;
- (id) proxyForJson;
#end
Item.m
#implementation Item
#synthesize ItemId;
#synthesize SequenceId;
#synthesize Description;
#synthesize IsOpen;
- (id) proxyForJson {
return [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithFormat:#"%i", ItemId], #"ItemId",
SequenceId, #"SequenceId",
Description, #"Description",
IsScanned, #"IsOpen",
nil ];
}
#end
UPDATE
Student example
I tried to make a separate project. I copied to new project all from classes directory of sbjson framework. This is code:
#import "SBJson.h"
#interface Student : NSObject
{
NSString *name;
NSInteger sid;
NSString *email;
}
#property NSString *name;
#property NSInteger sid;
#property NSString *email;
- (id) proxyForJson;
#end
#implementation Student
#synthesize name;
#synthesize sid;
#synthesize email;
- (id) proxyForJson{
return [NSDictionary dictionaryWithObjectsAndKeys:
name, #"student_name",
[NSNumber numberWithInt:sid], #"student_id",
email, #"email",
nil ];
}
#end
NSMutableArray* studentArray = [[NSMutableArray alloc]init];
Student* s1 = [[Student alloc]init];
s1.name = #"student 1";
s1.sid = 45;
s1.email = #"test#test.com";
Student* s2 = [[Student alloc]init];
s2.name = #"student 2";
s2.sid = 46;
s2.email = #"plavi#test.com";
[studentArray addObject:s1];
[studentArray addObject:s2];
NSString *jsonString = [studentArray JSONRepresentation];
NSLog(#"%#", jsonString);
And again I get error:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSArrayM
JSONRepresentation]: unrecognized selector sent to instance 0x741b100'

SBJson doesn't support serialising user-defined classes without assistance. If you implement a -proxyForJson method in your Person class (example here) it should work, however.
If you're using a recent Xcode the below should work. Header:
#interface Item : NSObject
#property int ItemId;
#property int SequenceId;
#property BOOL IsOpen;
#property(copy) NSString* Description;
- (id) proxyForJson;
#end
Implementation:
#implementation Item
- (id) proxyForJson {
return #{ #"ItemId": #(self.ItemId),
#"SequenceId": #(self.SequenceId),
#"Description": self.Description,
#"IsOpen": #(self.IsOpen)
};
}
#end
This should let SBJson serialise the Item objects to NSDictionaries. However, SBJson does not support parsing JSON into custom objects. So you will always get this back in the dictionary form. I don't know of any Objective-C JSON parser that provides bindings to custom types.

I would suggest reading the top two comments of this thread. If those don't help, it is still very likely that you are not installing the library correctly. Try removing the SBJSON files from your project and then readding them, making sure that they are added to your target. Also, make sure you are importing the SBJSON header into your class.
I would suggest that you try using JSONRepresentation on an array of NSString objects. If the framework is correctly installed, this should definitely work. This way you can narrow down whether it is an installation issue or whether it is an issue with your custom class.

Check out the following excerpt from Working with JSON in iOS 5 Tutorial
This is mainly for generating JSON.
//build an info object and convert to json
NSDictionary* info = [NSDictionary dictionaryWithObjectsAndKeys:
[loan objectForKey:#"name"],
#"who",
[(NSDictionary*)[loan objectForKey:#"location"]
objectForKey:#"country"],
#"where",
[NSNumber numberWithFloat: outstandingAmount],
#"what",nil];
//convert object to data
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:info options:NSJSONWritingPrettyPrinted error:&error];
Now, the difference lies in using NSDictionary and converting that into JSON Data. Try forming the JSON in the way given above and check if the problem persists.

you are correctly linking the category? to me it kinda looks like you are missing a category

Related

Unable to create mutable copy of NSDictionary

UPDATE: Now working (added fixes as suggested - Thanks!)
I've been trying to clone an NSDictionary of employee info. The main NSDictionary is created in a different class and passed along in prepareForSegue. I want to be able to create a mutable copy of that NSDictionary in another class which can then update the employee info and send it off to another class for processing so I still have the original unchanged dataset to work with at a later time. I've found a few different examples on Stack, but nothing I could get working. When I break on the btn_click method and examine the local pp object after the ..objectForKey call, pp is still nil. What have I done wrong here?
obj_person.h
#import <Foundation/Foundation.h>
#interface obj_person : NSObject
#property (strong,nonatomic) NSString *personID;
#property (strong, nonatomic) NSString *personName;
#property (strong, nonatomic) NSString *personTitle;
#end
obj_person.m
#import "obj_person.h"
#implementation obj_person
#synthesize personID = _personID;
#synthesize personName = _personName;
#synthesize personTitle = _personTitle;
#end
viewcontroller.m
#import "ViewController.h"
#import "obj_person.h"
#interface ViewController ()
#end
#implementation ViewController
int mCounter = 1;
NSMutableDictionary *mCopy;
NSMutableDictionary *mNsd;
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *arnames = [[NSArray alloc] initWithObjects:#"mary", #"jane", #"stan", #"cartman", nil];
NSArray *arkeys = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2],[NSNumber numberWithInt:3], [NSNumber numberWithInt:4], nil];
mNsd = [[NSMutableDictionary alloc] initWithCapacity:[arnames count]];
int i = 0;
for (NSString *name in arnames)
{
obj_person *p = [[obj_person alloc] init];
p.personID = [arkeys objectAtIndex:i];
p.personName = name;
[mNsd setObject:p forKey:p.personID];
i++;
}
mCopy = [mNsd mutableCopy];
}
- (IBAction)btn_click:(id)sender
{
NSLog (#"%d original items", [mNsd count]);
obj_person *pp = [mCopy objectForKey:[NSNumber numberWithInt:mCounter]];
NSLog(#"%#", pp.personName);
pp.personName = #"Gerald";
if (++mCounter > [mCopy count])
mCounter = 1;
}
#end
Don't define:
NSMutableDictionary *mCopy;
NSMutableDictionary *mNsd;
Outside of the #interface and #implementation. They should be instance variables, so define instance variables or use properties to define them.
It's a good job you don't use n from:
for (NSArray *n in arnames)
because it isn't an NSArray, it's an NSString. You should fix that and you should probably both name it better than n and use it.
This:
obj_person *pp = [mCopy objectForKey:[NSNumber numberWithInt:1]];
fails because the key you originally stored with is an NSString instance and the thing you are using to try to get the data out is an NSNumber instance (so they can never match).
You might try:
mCopy = [mNsd mutableCopy];
[mCopy retain]
One theory is that the mutableCopy returns is an autoreleased object and it's being killed off before the btn_click function fires. According to this post: Retain/release of returned objects, mutableCopy should not be autoreleasing the array, but bugs do happen.
Else, maybe try iterating through with a for-loop instead.
int cnt = [arnames count];
for(int i=0; i<cnt; i++)
...

JSONKit with Key-Value Coding and BOOLs

I'm attempting to serialize various objects by using Key-Value Coding to convert them to an NSDictionary, then JSONKit to serialize the NSDictionary to an NSString/NSData. I'm running into problems converting BOOL properties.
The KVC guidelines state that valueForKey: will, for BOOL properties, create an NSNumber via [NSNumber numberWithBool:]. JSONKit states that NSNumbers created via numberWithBool: will be serialized to true/false. I've tested JSONKit's claim and it works. However, when I access a BOOL value with KVC, I get an object which does not look like it was created via numberWithBool:. In particular, it does not evaluate equal to kCFBooleanTrue, which JSONKit uses as a marker for a boolean. The end result is that my BOOL properties are serialized to 0/1 instead of true/false, which is causing problems for the receiving API.
How do I determine if an NSNumber from KVC came from a BOOL property? Am I misreading Apple's documentation? Or is there some other way to get this serialization procedure to work?
Below is the test which is failing:
#import "JSONKit.h"
- (void) testCompareKVCBoolToNumberWithBool {
NSNumber *numberBool = [NSNumber numberWithBool:YES];
//This passes
STAssertTrue(numberBool == (id)kCFBooleanTrue, #"Number %# should kCFBooleanTrue.", numberBool);
TestModel *model = [[TestModel alloc] init];
model.boolProperty = YES;
NSNumber *kvcBool = [model valueForKey:#"boolProperty"];
//This fails
STAssertTrue(kvcBool == (id)kCFBooleanTrue, #"Number %# should be a kCFBooleanTrue.", kvcBool);
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
numberBool, #"numberBool",
kvcBool, #"kvcBool",
nil];
NSString *jsonString = [dict JSONString];
//This yields: jsonString: {"kvcBool":1,"numberBool":true}
NSLog(#"jsonString: %#", jsonString);
}
And here is the TestModel code:
#interface TestModel : NSObject
#property (assign) BOOL boolProperty;
#end
#implementation TestModel
#synthesize boolProperty = _boolProperty;
#end
Thanks!
You may want to checkout my implementation which does this automatically - https://github.com/QBurst/KVCObjectSerializer

Using setValuesForKeysWithDictionary with child objects and JSON

I have a json string
{"name":"test","bar":{"name":"testBar"}}
In objective c I have an object
#interface Foo : NSObject {
}
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) Bar * bar;
#end
And I just synthesize those properties. And I have a child object with synthesized properties.
#interface Bar : NSObject {
}
#property (nonatomic, retain) NSString * name;
#end
Then here is the code where I'm trying to get into the Foo object where response is the json string above:
SBJsonParser *json = [[SBJsonParser new] autorelease];
parsedResponse = [json objectWithString:response error:&error];
Foo * obj = [[Foo new] autorelease];
[obj setValuesForKeysWithDictionary:parsedResponse];
NSLog(#"bar name %#", obj.bar.name);
This throws an exception on the NSLog statement of:
-[__NSCFDictionary name]: unrecognized selector sent to instance 0x692ed70'
But if I change the code to it works:
NSLog(#"bar name %#", [obj.bar valueForKey:#"name"]);
I'm confused at why I can't do the first example, or am I doing something wrong?
Have you tried this?
// Foo class
-(void)setBar:(id)bar
{
if ([bar class] == [NSDictionary class]) {
_bar = [Bar new];
[_bar setValuesForKeysWithDictionary:bar];
}
else
{
_bar = bar;
}
}
-setValuesForKeysWithDictionary: isn't smart enough to recognize that the value of the key "bar" should be an instance of Bar. It's assigning an NSDictionary to that property. Thus, when you ask for the property "name," the dictionary can't field that request. However, an NSDictionary does know how to handle -valueForKey:, so it happens to work in that case.
So you need to use something smarter than -setValuesForKeysWithDictionary: to populate your objects.

Problem creating NSManagedObject derived class

I am doing something wrong here... I know that
I'm using Xcode and I have created the following class using the data modeller:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Project : NSManagedObject {
#private
}
#property (nonatomic, retain) NSNumber * indent;
#property (nonatomic, retain) NSNumber * collapsed;
#property (nonatomic, retain) NSString * color;
#property (nonatomic, retain) NSNumber * project_id;
#property (nonatomic, retain) NSNumber * item_order;
#property (nonatomic, retain) NSNumber * cache_count;
#property (nonatomic, retain) NSNumber * user_id;
#property (nonatomic, retain) NSString * name;
#end
When I am trying to propagate this class with data from a JSON source using the following code:
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"projects" ofType:#"json"];
if (filePath) {
NSString* jsonString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
DLog(#"JSON for Projects:%#", jsonString);
SBJsonParser* jsonParser = [SBJsonParser new];
id response = [jsonParser objectWithString:jsonString];
NSArray* array = (NSArray*) response;
NSEnumerator* e = [array objectEnumerator];
NSDictionary* dictionary;
while ((dictionary = (NSDictionary*)[e nextObject])) {
Project* project = [[Project alloc] init];
project.user_id = [dictionary objectForKey:#"user_id"];
project.name = [dictionary objectForKey:#"name"];
project.color = [dictionary objectForKey:#"color"];
project.collapsed = [dictionary objectForKey:#"collapsed"];
project.item_order = [dictionary objectForKey:#"item_order"];
project.cache_count = [dictionary objectForKey:#"cache_count"];
project.indent = [dictionary objectForKey:#"indent"];
project.project_id = [dictionary objectForKey:#"project_id"];
[elementArray addObject:project];
[project release];
}
}
However, the code stops at the project.user_id = [dictionary objectForKey:#"user_id"]; line with an exception "* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Project setUser_id:]: unrecognized selector sent to instance 0x590bcb0'"
I don't know why this is happening or how to resolve this.
I've set up a reality distortion field so I don't violate my NDA. And now I can answer your question, it has nothing to do with the product-that-must-not-be-named anyway.
There is your bug: Project* project = [[Project alloc] init];
The #dynamic setters and getters are not created for you if you create your object this way.
You can't use NSManagedObjects without a NSManagedObjectContext.
You should use something like this:
Project *project = (Project *)[NSEntityDescription insertNewObjectForEntityForName:#"Project" inManagedObjectContext:self.managedObjectContext];
Property names with underscores are not very sensible in the Objective C world - I guess the properties generated by Core Data have the wrong names therefore. Try using CamelCase, that is calling your properties userID, itemOrder, cacheCount etc.
You may need to set up your getters and setters.
It could be as simple as adding:
#synthesize user_id;
In your class file.

Objective-C for Dummies: How do I loop through an NSDictionary inside of an NSDictionary?

Alright guys, I'm quite confused. So, I have an NSDictionary which is populated by a JSON string which looks like:
{"Success":true,"Devices":[{"UDId":"...","User":"...","Latitude":0.0,"Longitude":0.0}]}
Now, I know how to check if Success is true, but I need to loop through the array of Devices (JSON object) and create an internal array of Devices (internal app object) and I have no idea how to do that. Can someone please explain how to do it?
Here's my Device.m/h:
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#interface Device : NSObject {
NSString *udId;
NSString *name;
NSNumber *latitude;
NSNumber *longitude;
}
#property (nonatomic, retain) NSString *udId;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSNumber *latitude;
#property (nonatomic, retain) NSNumber *longitude;
#pragma mark -
#pragma mark MKAnnotation Properties
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#end
----
#import "Device.h"
#implementation Device
#synthesize udId, name, latitude, longitude;
- (CLLocationCoordinate2D)coordinate {
CLLocationCoordinate2D internalCoordinate;
internalCoordinate.latitude = [self.latitude doubleValue];
internalCoordinate.longitude = [self.longitude doubleValue];
return internalCoordinate;
}
- (void)dealloc {
[udId release];
udId = nil;
[name release];
name = nil;
[latitude release];
latitude = nil;
[longitude release];
longitude = nil;
[super dealloc];
}
#end
And here's the methods where I should be reading the response and converting it to objects I can use:
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
I'd really appreciate some help on this. I just can't seem to wrap my head around it...
Thanks in advance!
You are almost there. In your code where you say:
// READ "DEVICES" AND CONVERT TO OBJECTS
do this:
NSArray * devices = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary * deviceInfo in devices) {
Device * d = [[[Device alloc] init] autorelease];
[d setLatitude:[deviceInfo objectForKey:#"Latitude"]];
[d setLongitude:[deviceInfo objectForKey:#"Longitude"]];
[d setName:[deviceInfo objectForKey:#"User"]];
[d setUdId:[deviceInfo objectForKey:#"UDId"]];
// do some stuff with d
}
What's going on here: I didn't see what JSON library you are using to convert, but presuming it works like TouchJSON or SBJSON, the JSON array is automatically turned into an NSArray instance, while the inner hashes of the NSArray are NSDictionary objects. At the point that you have deserialized that JSON string, everything you're dealing with will be instances of NSString, NSNumber, NSArray and NSDictionary (and depending on the library, NSNull to represent null values).
First you need to define your initializer/constructor for your Device class.
Device.h
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon;
Device.m
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon {
if (self = [super init]) {
self.udid = udid;
self.name = name;
self.latitude = lat;
self.longitude = lon;
}
return self;
}
Then you can initialize a new object like:
Device *dev = [[Device alloc] initWithUdid:#"a udid" name:#"the name" latitude:latNum longitude:lonNum];
So, you should be able to iterate the array and build your Device objects like so:
NSArray *devicesArray = [dict objectForKey:#"Devices"];
for (NSDictionary *d in devicesArray) {
Device *dev = [[Device alloc] initWithUdid:[d objectForKey:#"UDId"]
name:[d objectForKey:#"User"]
latitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]
longitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]];
}
You want to access the array of device dictionaries from the top-level dictionary just as you did the Success value. Then iterating over the dictionaries you can use each's -keyEnumerator method to iterate over its keys.
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
NSArray* deviceArray = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary* dict in deviceArray)
{
for(NSString* key in [dict keyEnumerator])
{
NSLog(#"%# -> %#", key, [dict objectForKey:key]);
}
}
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
Sounds like you need to reuse your line:
[jsonDictionary objectForKey:#"Success"]
try having a look at
[jsonDictionary objectForKey:#"Devices"]
You really need to figure out what type it returns.
If you're lucky, it returns an NSDictionary, or alternately something that you can easily turn into an NSDictionary.