How do I convert a JSON structure to an object in Objective-C? - objective-c

I have a JSON structure like this:
{
id:100
contactInfo: {
name: John Doe
city: New York
}
}
and a corresponding Customer class w/ the following properties including a nested contactInfo class.
Customer.id
Customer.contactInfo.name
Customer.contactInfo.city
Is it possible to convert the JSON data directly to an instance of the Customer class?
I know how to get an NSDictionary object, but that is very cumbersome and verbose to work with and would rather convert/deserialize the data directly to the Customer class.
EDITED w/ Additional Info:
In other languages I have worked with there is built-in support for deserialization of JSON to custom objects. All you have to do is mark the class as "Serializable" and the JSON is deserialized w/o the need to write custom code in each class.
The NSDictionary object can become very cumbersome if the class has nested classes. It would be much easier to use the custom object properties like this
Customer.contactInfo.name
rather than
[(NSDictionary*)[customerDict objectForKey:#"contactInfo"] objectForKey:#"name"]
thus my question about a standard built-in deserializer in Objective-C.

As of iOS5 there if official support for JSON
https://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html

I don't know why you think it is cumbersome to work with. A simple way to do it would be to have an initialiser for your customer object that took a dictionary as a parameter.
That way you could get your downloaded data as JSON, deserialise it into a dictionary and then pass it to the initializer to create an object from the dictionary.

Any of the dozens of JSON libraries will parse your JSON string and turn it into an NSDictionary. You're going to have to deal with an intermediate format if you don't want to write a parser yourself. But, once you have the NSDictionary you can use Key Value Coding (KVC) to set the properties on your object:
for (NSString *key in jsonDictionary) {
[customer setValue:[jsonDictionary objectForKey] forKey:key];
}
KVC is built into Cocoa, and defines setValue:forKey: for all objects, so you can set properties by name regardless of how they are defined.
You'll have to do something smarter than the loop above to handle your internal ContactInfo class, but you should be able to figure that out. For safety (since the JSON is probably coming over the network) you might want to make a whitelist of keys and loop over that, and use valueForKeyPath:/setValue:forKeyPath: to handle nested objects.

Consider using RestKit api (http://restkit.org/) and its object mapping system...
Here is some wiki : https://github.com/RestKit/RestKit/wiki/Object-mapping

Write an initializer for your Customer class that takes JSON data as a parameter, something like:
-(id)initWithJSONData:(NSData*)data;
You can implement that method to do what Abizern suggests: read the data into a dictionary, and then pull whatever values you need out of the dictionary.
If you want to get fancy, another way you could go is to adopt NSCoding in your Customer class, and then create a NSCoder subclass that deserializes JSON data. This could work very much like NSKeyedUnarchiver. Doesn't seem worth the trouble unless you're going to take the same approach with a number of your classes, though.

Using this library, you can do the following:
SBJSON *parser = [[SBJSON alloc] init];
NSDictionary *customer = [parser objectWithString:jsonString];
You can then access your properties using:
[customer objectForKey:#"id"];
[[customer objectForKey:#"contactInfo"] objectForKey:#"name"];
[[customer objectForKey:#"contactInfo"] objectForKey:#"city"];
You can then use this code in your Customer class' init function. For example:
- (id)initWithJSON:(NSString *)jsonString {
self = [super init]; // or whatever you need here
if (self) {
SBJSON *parser = [[SBJSON alloc] init];
NSDictionary *customer = [parser objectWithString:jsonString];
self.id = [customer objectForKey:#"id"];
self.contactInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[[customer objectforKey:#"contactInfo"] objectForKey:#"name"], #"name", [[customer objectforKey:#"contactInfo"] objectForKey:#"city"], #"city"]
}
return self;
}

Related

Convert NSDictionary object to Object

I'm receiving a JSON payload of data from a MVC API in my iOS application. NSJSONSerialization then serializes this into an object. This object contains some properties and also a list of data objects. The data objects are of type NSDictionary. I already have the class structure of these objects in ObjC (Im using odata so i want to convert the objects to their OdataObject equivalent).
So I'd like to know how I can cast/convert these NSDictionary objects to their corresponding OdataObject class (or any object really)?
You can't cast an NSDictionary instance to be an OdataObject, you either need to explicitly convert the instance or create the appropriate instance when you deserialise the JSON.
You could look at using setValuesForKeysWithDictionary: to push your dictionary contents into another instance using KVC. Whether this will work in this case depends on the OdataObject definition (from github? Not convinced) and the dictionary contents...
Write a class category for NSDictionary that allows the conversion to the OdataObject class? I'm sorry, I don't completely understand what you're asking but if you need to be able to convert NSDictionary to a custom object, then I recommend Class Categories:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html
Yes, you can not cast your NSDictionary instance to your custom model object. For that you need to write code of conversion.
1) Create a class which inherits NSObject with required properties.
2) Synthesize all the properties
3) Write one private keyMapping method which returns the dictionary with keys you want in your model object as
-(NSDictionary *)keyMapping {
return [[NSDictionary alloc] initWithObjectsAndKeys:
#"key1", #"key1",
#"key2", #"key2",
#"key3", #"key3",
#"key4", #"key4",
#"key5", #"key5",
nil];
}
4) Write class method which takes NSDictionary instance, as a parameter and returns instance of the same model class with filled values from NSDictionary as (Pass your dictionary to this method)
+(ModelClass *)getModelClassObjectFromDictionary:(NSDictionary *)dictionary {
ModelClass *obj = [[ModelClass alloc] init];
NSDictionary *mapping = [obj jsonMapping];
for (NSString *attribute in [mapping allKeys]){
NSString *classProperty = [mapping objectForKey:attribute];
NSString *attributeValue = [dictionary objectForKey:attribute];
if (attributeValue!=nil&&!([attributeValue isKindOfClass:[NSNull class]])) {
[obj setValue:attributeValue forKeyPath:classProperty];
}
}
return obj;
}
Thats it. Hope this helps you.

Objective C Convenience Method Use

I am tring to understand convenience methods.
IF I have a sqlite database containing store details and am returning these store details in a FMResultSet. I am thinking that to create an array of these store details as Store objects, that the best way would be create an object of type Store in one go in a convenience method and add to array.
The Class I have created is as below with convenience method
#interface StoreDetails : NSObject
#property (nonatomic, strong) NSString *storeName;
etc etc etc
+ (instancetype)storeWithStoreName:(NSString *)storeName
TelephoneNumber:(NSString *)
telephoneNumber: etc .......
My ResultSet loop would be as below?
NSMutableArray *Stores = [[NSMutableArray alloc] init];
while ([rs next]) {
Store *store =
[Store storeDetailsWithStoreName:[rs stringForColumn:#"storename"]
telephoneNumber:[rs stringForColumn:#"TelephoneNo"]];
[Stores addObject:store];
}
Is my thinking correct as above is is it better to go as below.
NSMutableArray *Stores = [[NSMutableArray alloc] init];
while ([rs next]) {
Store *store = [Store alloc] init];
store.storeName = [rs stringForColumn:#"storename"];
store.telephoneNumber = [rs stringForColumn:#"TelephoneNo"];
[Stores addObject:store];
}
All I am trying trying to understand is why you would use one over the other in noob speak, thankyou.
I think you have a good approach: initializing your Store object in a method of the Store class.
The storeDetailsWithStoreName:... method you have defined is a good example of what Apple calls a factory method (assuming you aren't doing anything weird in its implementation). It's a quite common pattern; Foundation has all sorts of examples: arrayWithCapacity:, numberWithInt:, etc.
With ARC, the simplest examples of these factory methods are nearly identical to a corresponding alloc/init expression, since the developer no longer has to think about autoreleasing objects. But there are still plenty of uses for factory methods, e.g. special instantiation patterns such as singleton or flyweight, including a small amount of common conversion or formatting code for convenience, implementing class clusters, etc. And there's the simple convenience of not having an extra set of brackets and less indentation.
The instancetype keyword is a good choice. This allows you to send the same message to a subclass of Store, with the expectation that the method will instantiate an object of the subclass using the same init method, like this:
+ (instancetype)storeWithStoreName:(NSString *)storeName
telephoneNumber:(NSString *)
...
{
return [[self alloc] initWithStoreName:...];
}
In the code above, as it's a class method, the self in [self alloc] is the Class object (either Store or a subclass of Store) rather than a specific instance of Store. This is what allows creating an instance of the correct class at runtime, depending on whether you call [Store storeWithStoreName:...] or [MoreSpecificStoreSubType storeWithStoreName:...].
The alternative to a factory method, or compliment to it really, is to declare a custom init method in your Store class:
- (id)initWithStoreName:(NSString *)storeName
telephoneNumber:(NSString *)telephoneNumber ...
…and use that directly inside your loop, instead of a factory method. Again, with ARC, not much of a difference between the two unless there's extra work you want to do in the factory method. You can have multiple variants of the init method; the standard practice is for all of them to call the most detailed init method, which is called the designated initializer.
I would recommend taking the time to read the Apple documentation pages on standards for class design (I linked to some of these pages above). Since there are a lot of this is based more on convention rather than language design restrictions, it's important to know all about the patterns and best practices for good design and proper behavior of special methods.

Objective-C runtime issue setting object for NSMutableDictionary

I am coding a fairly complex system that uses a lot of metadata to manage dynamic objects. I am using various objective-c runtime features. I want to add stuff to mutable dictionaries which are properties within various classes. I want to do this where I know the class type and I know the property name, but I don't want to 'hard code' the assignment. If I hard-coded it I could do this:
[[(myknownclass*)localClassObjectInstance knownDictionary] setObject:value forKey:key];
but what I want to do is something like this:
[[unknownClassObjectInstance {aStringContainingTheDictionaryName}] setObject:value forKey:key];
How can I reference the mutable dictionary property when I only have the name of the property at runtime?
You can use -valueForKey: to run the method:
[(NSMutableDictionary*)[inst valueForKey:#"whateverMethodName"] setObject:o forKey:k];
Or alternatively, you can use performSelector:
SEL dictGetter = NSSelectorFromString(#"whateverMethodName");
NSMutableDictionary* dict = [inst performSelector:dictGetter];
[dict setObject:o forKey:k];

Cocoa: deserialize json string to custom objects (not NSDictionary, NSArray)

In java-land, there are a handful of useful libraries which will convert json strings to objects of matching type. The json libraries I've seen for cocoa simply create nested NSDictionaries and NSArrays. Is there a tool out there which will go the extra step of reconstituting whatever object type I want?
So, for example, if I have a class called "Unicorn", with a property "maneColor", and I have json that looks like this:
{
"maneColor":"silver"
}
I can automatically instantiate a Unicorn object with "maneColor" set to "silver".
I'm not aware of any specific implementations, but key-value coding gets you very close to what you want: Key Value Coding Guide. I've had good results combining streamed json parsing with KVC.
The -setValue:forKey: method makes adapting serialized data to custom objects fairly straightforward. To continue with your example, you'd create a Unicorn class with all required accessor methods: -setName:/-name, -setManeColor/-maneColor, etc. (You may be able to use properties for some expected values, but there are cases, as with the maneColor value, where you probably want to write a custom setter to convert from the color name string to an NSColor or UIColor object.)
You'll also want to add two more methods to your custom object: -setValue:forUndefinedKey: and -valueForUndefinedKey:. These are the methods that will be called if your object has no accessor methods matching a key passed into the KVC methods. You can catch unexpected or unsupported values here, and store them or ignore them as necessary.
When you send -setValue:forKey: to the Unicorn object, the framework looks for accessors matching the key pattern. For instance, if the key is "maneColor" and you're setting the value, the framework checks to see if your object implements -setManeColor:. If so, it invokes that method, passing in the value; otherwise, -setValue:forUndefinedKey: is called, and if your object doesn't implement it, an exception is thrown.
When your parser's delegate receives notification that parsing a json unicorn object has begun, instantiate a Unicorn object. As your parser returns the parsed data to you, use -setValue:forKey: to add the data to your object:
- ( void )parserDidBeginParsingDictionary: (SomeParser *)p
{
self.currentUnicorn = [ Unicorn unicorn ];
}
- ( void )parser: (SomeParser *)p didParseString: (NSString *)string
forKey: (NSString *)key
{
[ self.currentUnicorn setValue: string forKey: key ]
}
- ( void )parserDidFinishParsingDictionary: (SomeParser *)p
{
[ self.unicorns addObject: self.currentUnicorn ];
}
Use Jastor - https://github.com/elado/jastor
Takes already parsed JSON into NSDictionary and fills an instance of real Objective-C class.
NSDictionary *parsedJSON = (yajl, JSONKit etc)
Unicorn *unicorn = [[Unicorn alloc] initWithDictionary:parsedJSON];
unicorn.maneColor // "silver"
As any subclass of NSObject conforms to NSKeyValueCoding protocol:
NSDictionary *parsedJSON = //whatever
id <NSKeyValueCoding> entity = [[CustomNSObjectSubclass alloc] init];
[entity setValuesForKeysWithDictionary:parsedJSON];
Apple added the NSJSONSerialization class to iOS 5.0 which, according to the documentation, does the following:
You use the NSJSONSerialization class to convert JSON to Foundation
objects and convert Foundation objects to JSON.
An object that may be converted to JSON must have the following
properties:
The top level object is an NSArray or NSDictionary. All objects are
instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull. All
dictionary keys are instances of NSString. Numbers are not NaN or
infinity.
Here's a tutorial and wrapper method to get you started.

How to store the NSArray with customized Object?

I have an NSMutableArray, if this is store string, I can read, and write it successfully, using this method.
[array writeToFile:m_sApplicationPlistPath atomically:YES];
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:m_sApplicationPlistPath];
but if my Array add something which is not a simple string, for example, I add a special Object to the array, like this:
[array addObject:[[SpecialObject alloc] init]];
I find that I can't read back the special object, how can I solve it, thank you.
Implement the NSCoding protocol. (encodeWithCoder and initWithCoder)
If you still cannot use the method writeToFile:atomically: then you have to serialize the NSArray to get data, and then write it to an file.
NSData *data = NSKeyedArchiver archivedDataWithRootObject:theArray];
I think you can't do that because [NSArray writeToFile:atomically:] method makes use of property lists, which are only available for certain data types (NSString, NSData, NSArray or NSDictionary).
In order for you to write the array to a file you can make SpecialObject comply to NSCoding protocol and then save the array using -[NSKeyedArchiver archiveRootObject:toFile:] using the array as root object.
You can get this to work by using methods defined in the NSKeyValueCoding protocol to convert the instances of your custom class to instances of NSDictionary before you write them. You could do a similar conversion after reading the plist file back in to recreate the objects.
Here's an example that converts an array of instances of a custom Book class to an array of dictionaries, using the dictionaryWithValuesForKeys: method declared in NSKeyValueCoding (and implemented by NSObject, so all objects inherit this behavior):
+ (NSArray *)dictionariesFromBooks:(NSArray *)books
{
NSMutableArray *bookDicts = [NSMutableArray arrayWithCapacity:[books count]];
for (Book *currBook in books)
{
NSDictionary *currDict = [currBook dictionaryWithValuesForKeys:[Book keys]];
[bookDicts addObject:currDict];
}
return bookDicts;
}
Similarly, here's a method that populates instances of Book using the NSKeyValueCoding method setValuesForKeysWithDictionary:
+ (NSArray *)booksFromDictionaries:(NSArray *)bookDicts
{
NSMutableArray *books = [NSMutableArray arrayWithCapacity:[bookDicts count]];
for (NSDictionary *currDict in bookDicts)
{
Book *currBook = [[Book alloc] init];
[currBook setValuesForKeysWithDictionary:currDict];
[books addObject:currBook];
[currBook release];
}
return books;
}
With a little work, this can be made to cascade to nested custom objects. For example, if Book contained a nested Author instance, you could override NSKeyValueCoding methods such as setValue:forKey: and dictionaryWithValuesForKeys: to convert the instance on the fly.
From docs:
If the array’s contents are all property list objects (NSString, NSData, NSArray, or NSDictionary objects), the file written by this method can be used to initialize a new array with the class method arrayWithContentsOfFile: or the instance method initWithContentsOfFile:. This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.