JSONKit with Key-Value Coding and BOOLs - objective-c

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

Related

SBJsonParser can't parse NSArray

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

[NSObject description]

Hello can you give me an example of the usage of this method
+(NSString *)description
Do I use description with an instance of a NSObject (any kind of object) or NSString?
or do I use without an instance, directly using NSObject (any kind of object) or NSString?
The description of the instance gives you information about the specific instance you have created.
- (NSString *)description;
NSString *string = [NSString alloc] initwithString:#"aString"]];
[string description];
Gives you information about this instance (location in memory etc)
On the other side:
+ (NSString *)description;
[NSString description];
Gives you information about the class NSString.
The same rules apply to all NSObject subclasses and other classes that conform to NSObject protocol such NSArray, NSDictionary *NSProxy* etc
Let's say we have:
#interface randomObject : NSObject
{
NSString *yourString;
}
and somewhere:
yourString = [[NSString alloc] initWithString:#"random text"];
then we can override randomObject like this...
- (NSString *)description
{
return [NSString stringWithFormat:#"%#", yourString];
}
after we done this we can call a NSLog with our NSObject:
-(void)viewDidLoad {
randomObject *ourObj;
ourObj = [[randomObject alloc] init];
NSLog(#"%#", ourObj); // this will output "random text"
}
You seem to mainly be confused about the difference between class and instance methods.
NSObject declares the class method +[NSObject description], which, as the docs tell you "Returns a string that represents the contents of the receiving class.". If you send the message description to a class object, like so:
[NSArray description];
[NSNumber description];
[[someObject class] description];
this method will be called and you'll get the string the class uses to describe itself.
On the other hand, the NSObject protocol declares a required instance method -[id<NSObject> description], which will return "a string that describes the contents of the receiver". When you send this to an instance, you get a string representing it:
NSNumber * n = [NSNumber numberWithInt:10];
[n description];
NSArray * arr = [NSArray arrayWithObjects:#"Lemon", #"curry", #"?", nil];
[arr description];
NSDicitonary * d = [NSDictionary dictionaryWithObject:arr forKey:n];
[d description];
All subclasses of NSObject inherit both of these methods, and they can be overridden just like any other. Notice, for example, that NSDictionary and NSArray format themselves and send description to the objects they contain.
It should also be noted that, when using NSLog(), the %# format specifier causes description to be sent to its argument (whether it's a class object or an instance).
The call most normally used it actually
- (NSString *)description;
It is used on normally instances, not classes. It can be overridden in custom classes to provide detailed information about an object. If you attempt to access a class as as string, the description method will automatically be called.
NSLog(#"array: %#", array); //Identical
NSLog(#"array: %#", [array description]); //Identical
You can use it on classes just as you stated
[NSArray description];
+(NSString *)description
Is used mainly for debug and is used by instances. It allows to print a description of the object.

SBJSON encode Object who contain an array of another Object

i've a little json encode problem :
i need to encode an object format JSON with SBJSON before send it to a php server
At the moment this sample code work :
NSArray *arrayData = [NSArray arrayWithObjects:
user.id == nil ? [NSNumber numberWithInt:-1] : user.id,
ProfessionField.text, NameField.text, RPPSField.text, RPPSField.text,
NameField.text, SurnameField.text, StreetField.text,
TownField.text, CpField.text, MailField.text,
PhoneField.text, FaxField.text, MobileField.text,
// [user.horaires JSONRepresentation],
nil];
NSArray *arrayKey = [NSArray arrayWithObjects:
#"id", #"spe", #"name", #"rpps", #"cip",
#"name", #"surname", #"rue",
#"ville", #"cp", #"mail",
#"tel", #"fax", #"port",
// #"horaires",
nil];
NSDictionary *dataBrut = [NSDictionary dictionaryWithObjects:arrayData forKeys:arrayKey];
NSDictionary *jsonDict = [NSDictionary dictionaryWithObject:dataBrut forKey:#"data"];
NSString *jsonRequest = [jsonDict JSONRepresentation];
The problem is when i need to send the "user.horaires" (here in comment)
Application CRASH at the JSON representation of this object.
this object is an Array of the following class :
#interface Horaire : NSObject
{
BOOL morning;
}
#property (nonatomic, strong) NSNumber *id;
#property (nonatomic, strong) NSString *open;
#property (nonatomic, strong) NSString *close;
Someone know how to succes to encode this ?
You shouldn't be including the JSON representation as a JSON item. JSON does not "escape" string data very well, so the embedded JSON (unless you separately "escape" it) will cause parsing to choke.
Instead you should place the dictionary or array that was used to produce the JSON representation (ie, "user.horaires" itself) in the location where you show the representation being produced and inserted. Then the entire structure will be JSON-encoded in one operation.
Ie:
NSArray *arrayData = [NSArray arrayWithObjects:
user.id == nil ? [NSNumber numberWithInt:-1] : user.id,
ProfessionField.text, NameField.text, RPPSField.text, RPPSField.text,
NameField.text, SurnameField.text, StreetField.text,
TownField.text, CpField.text, MailField.text,
PhoneField.text, FaxField.text, MobileField.text,
user.horaires,
nil];

Can't save to plist

I have my own object class from inherited from NSObject
#interface BlockedCell : NSObject
{
NSValue *gridValue;
NSString *name;
}
#property (nonatomic, retain) NSValue *gridValue;
#property (nonatomic, retain) NSString *name;
#end
So I try to create a few objects:
NSMutableDictionary *dict = [[dict alloc] init];
for (int i = 0; i < 5; ++i)
{
BlockedCell *block = [[BlockedCell alloc] init];
block.gridValue = [NSValue valueWithCGPoint: CGPointMake(0.0f, 0.0f)];
block.name = #"something";
[dict setObject: block forKey: [NSString stringWithFormat: #"item_%d", i]];
[block release];
}
if([dict writeToFile: path atomic: YES])
NSLog(#"Saved");
else
NSLog(#"Failed to save");
[dict release];
And what I get for the output is "Failed to save"..
If my dictionary does not contains any data, then it will output "Saved"
EDIT:
After I did more testing, I found out that actually is the NSValue causing the saving failed.
So what should I do if I want to save CGPoint into plist?
As you discovered, property lists cannot store NSValue objects directly. The supported classes are NSData, NSString, NSArray, NSDictionary, NSDate, and NSNumber, as documented in the NSPropertyListSerialization Class Reference.
The easiest workaround would be to use NSString instead of NSValue:
block.gridString = NSStringFromCGPoint(CGPointZero);
CGPoint point = CGPointFromString(block.gridString);
you cant save NSValue directly.In your case you have to save a point in the form ofstring use below line
CGPoint point = CGPointMake(10.0,20.0)
//you need to translate the point into a compatible "Plist" object such as NSString
//luckily, there is a method for that
[rectArray addObject:NSStringFromPoint(point)];
//save into a plist
.....
on retrieval of this value
CGPoint Point = CGPointFromString([rectArray objectAtIndex:0]);

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.