iOS JSON serialization for NSObject-based classes - objective-c

I'd like to JSON-serialize my own custom classes. I'm working in Objective-C / iOS5.
I'd like something to do the following:
Person* person = [self getPerson ]; // Any custom object, NOT based on NSDictionary
NSString* jsonRepresentation = [JsonWriter stringWithObject:person ];
Person* clone = [JsonReader objectFromJson: jsonRepresentation withClass:[Person Class]];
It seems that NSJSONSerialization (and several other libraries) require the 'person' class to be based on NSDictionary etc. I want something that will serialize any custom object that I care to define (within reason).
Let's imagine Person.h looks like this:
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property NSString* firstname;
#property NSString* surname;
#end
I'd like the generated JSON for an instance to look similar to the following:
{"firstname":"Jenson","surname":"Button"}
My app uses ARC. I need something that will both serialise and deserialize using objects.
Many thanks.

This is a tricky one because the only data you can put into JSON are straight up simple objects (think NSString, NSArray, NSNumber…) but not custom classes or scalar types. Why? Without building all sorts of conditional statements to wrap all of those data types into those type of objects, a solution would be something like:
//at the top…
#import <objC/runtime.h>
NSMutableDictionary *muteDictionary = [NSMutableDictionary dictionary];
id YourClass = objc_getClass("YOURCLASSNAME");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(YourClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
SEL propertySelector = NSSelectorFromString(propertyName);
if ([classInstance respondsToSelector:propertySelector]) {
[muteDictionary setValue:[classInstance performSelector:propertySelector] forKey:propertyName];
}
}
NSError *jsonError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:muteDictionary options:0 error:&jsonError];
This is tricky, though because of what I stated before. If you have any scalar types or custom objects, the whole thing comes tumbling down. If it's really critical to get something like this going, I'd suggest looking into investing the time and looking at Ricard's links which allow you to see property types which would assist on the conditional statements needed to wrap the values into NSDictionary-safe objects.

Now you can solve this problem easily using JSONModel. JSONModel is a library that generically serialize/deserialize your object based on Class. You can even use non-nsobject based for property like int, short and float. It can also cater nested-complex JSON.
Deserialize example. By referring to your example, in header file:
#import "JSONModel.h"
#interface Person : JSONModel
#property (nonatomic, strong) NSString* firstname;
#property (nonatomic, strong) NSString* surname;
#end
in implementation file:
#import "JSONModelLib.h"
#import "yourPersonClass.h"
NSString *responseJSON = /*from somewhere*/;
Person *person = [[Person alloc] initWithString:responseJSON error:&err];
if (!err)
{
NSLog(#"%# %#", person.firstname, person.surname):
}
Serialize Example. In implementation file:
#import "JSONModelLib.h"
#import "yourPersonClass.h"
Person *person = [[Person alloc] init];
person.firstname = #"Jenson";
person.surname = #"Uee";
NSLog(#"%#", [person toJSONString]);

maybe this can help JLObjectStrip.
its the same as what jacob said but it iterates even to the property of the class. this will give you dictionary/array then just use sbjson/jsonkit or what ever you prefer to construct your json string.

Try this one BWJSONMatcher
It's really simple as well as convenient.
...
NSString *jsonString = #"{your-json-string}";
YourValueObject *dataModel = [YourValueObject fromJSONString:jsonString];
NSDictionary *jsonObject = #{your-json-object};
YourValueObject *dataModel = [YourValueObject fromJSONObject:jsonObject];
...
YourValueObject *dataModel = instance-of-your-value-object;
NSString *jsonString = [dataModel toJSONString];
NSDictionary *jsonObject = [dataModel toJSONObject];
...

What i do for my objects is i have a method called "toDict" that return a nsdictionary. IN this method i set all attributes i need/want into the dictionary for example
[user setObject:self.first_name forKey:#"first_name"];
[user setObject:self.last_name forKey:#"last_name"];
[user setObject:self.email forKey:#"email"];

Related

Should I be creating a class instead of a struct so that I can put the data into an NSArray?

Is it possible to create something like a C struct for Objective-C? I need to be able to use it in an NSArray so it cannot be a traditional struct. Right now I am declaring a whole class just to accomplish this and I was wondering if there is a simpler way.
What I currently have:
#interface TextureFile : NSObject
#property NSString *name;
#property GLKTextureInfo *info;
#end
#implementation TextureFile
#synthesize name = _name;
#synthesize info = _info;
#end
NSMutableArray *textures;
What I want to do:
typedef struct {
NSString *name;
GLKTextureInfo *info;
} TextureFile;
NSMutable array *textures;
It depends what kind of data you're using, the example you are using in your question seems okay for a struct.
If you need to store a C struct in an NSArray, which requires an object, you can convert the C-struct to NSValue and store it like that, you then convert back to its C struct type when you read it.
Check the Apple Documentation.
Given this struct:
typedef struct {
NSString *name;
GLKTextureInfo *info;
} TextureFile;
To store it:
TextureFile myStruct;
// set your stuct values
NSValue *anObj = [NSValue value:&myStruct withObjCType:#encode(TextureFile)];
NSArray *array = [NSArray arrayWithObjects:anObj, nil];
To read it again:
NSValue *anObj = [array objectAtIndex:0];
TextureFile myStruct;
[anObj getValue:&myStruct];

NSDictionary or custom NSObject for JSON parsing

I've seen a lot of people use NSDictionary for JSON parsing:
//ViewController.m
NSString* forename = [jsonDict valueForKey:#"forename"];
NSString* surname = [jsonDict valueForKey:#"surname"];
But I've also people creating custom NSObjects from a NSDictionary.
//JSONObject.h
#interface JSONObject : NSObject
#property (nonatomic) NSString* forename;
#property (nonatomic) NSString* surname;
#end
//JSONObject.m
#implementation JSONObect
#synthesize forename = _forename;
#synthesize surname = _surname;
#end
//ViewController.m
JSONObject* jsonObject = [[JSONObject alloc] init];
[jsonObject setForename:[jsonDict valueForKey:#"forename"]];
[jsonObject setSurname:[jsonDict valueForKey:#"surname"]];
And then store these in a NSMutableArray:
NSMutableArray* jsonObjectsArray = [NSMutableArray arrayWithCapacity:20];
[jsonObjectsArray addObject:jsonObject];
Which can be accessed later if needed.
In my case, I have a UITableView that gets it's data from JSON. The data is used at least once but most likely will be used more (eg. on device rotation). The JSON data shouldn't be permanently stored to file as it is updated regularly and is downloaded every time the app launches.
Should I use a custom NSObject or a NSDictionary in my scenario?
One argument for using a custom Object is that it is a few short steps away from using a NSManagedObject, which would let you leverage Core Data to manage your object graph.
The argument for using the NSDictionary is that it's simpler and easier to understand, and you define fewer "minor" classes (and associated h/m files), so less to manage in a project. Also a lot easier to edit/extend in a project "in flux".

How do I publicly declare the keys to an NSDictionary?

I have defined a dictionary that I would like to pass around to various other objects. When they receive this dictionary, they need to know how it is defined so they can unpack it to get its values. I've been using #define's in my public header to define the keys. That way, I get edit-time compiler checking to ensure I don't use a bum key. But is there some other, more standard way to declare the interface to a defined dictionary so that other objects will get compile errors if they try to use undefined keys?
Better than #define is to use constant NSStrings. In your header:
extern NSString * const MyDictionaryFribbleKey;
(That's a constant pointer to an NSString.) And in your implementation:
NSString * const MyDictonaryFribbleKey = #"theFribble";
This is how the frameworks export constant strings. This won't stop the use of invalid keys, nothing will really do that (it's C, you can bypass anything), but it raises the bar higher.
Instead of using a dictionary why not use an object?
The example below shows that there really isn't much extra work involved. Plus you gain the advantages of actually using objects.
Object Set up
#interface MyClass : NSObject
#property (nonatomic, strong) NSString *myString;
#property (nonatomic, assign) CGFloat myFloat;
#end
#implementation MyClass
#synthesize myString = _myString;
#synthesize myFloat = _myFloat;
#end
Object use
MyClass *myClass = [[MyClass alloc] init];
// Set
myClass.myString = #"aa";
myClass.myFloat = 12.0f;
// Get
NSString *myString = myClass.myString
CGFloat myFloat = myClass.myFloat;
NSDictionary Set up
.h
extern NSString * const MYClassKeyMyString;
extern NSString * const MYClassKeyMyFloat;
.m
NSString * const MYClassKeyMyString = #"MYClassKeyMyString";
NSString * const MYClassKeyMyFloat = #"MYClassKeyMyFloat";
NSDictionary use
NSDictionary *myDict = [[NSMutableDictionary alloc] init];
// Set
[myDict setObject:#"aa" forKey:MYClassKeyMyString];
[myDict setObject:[NSNumber numberWithFloat:12.0f] forKey:MYClassKeyMyFloat];
// Get
NSString *myString = [myDict objectForKey:MYClassKeyMyString];
CGFloat myFloat = [[myDict objectForKey:MYClassKeyMyFloat] floatValue];
That's a pretty standard way. You can also enumerate the keys of a dictionary using the -enumerateKeys method.

Localizing Core Data model properties for display

I'm working on an iOS project that uses a large and fairly complex data model. Some of the entities in the model have corresponding detail view controllers, which include table views that should display localized names and the corresponding values of certain properties.
I've looked at some of Apple's documentation for creating a strings file for a managed object model, but most of it seems geared toward displaying error messages generated by the SDK rather than accessing localized property names directly.
I created a strings file ("ModelModel.strings") for my model file ("Model.xcdatamodel"), and verified that it is loading correctly by looking at -localizationDictionary on my NSManagedObjectModel instance. My question is: how should I access the localized entity and property names in my code? Is there a way to get to them via NSEntityDescription, NSPropertyDescription, etc. or do I have to go through the NSManagedObjectModel every time?
I'm new at localization, so maybe the answer is obvious, but if so, feel free to just give me a nudge in the right direction.
Update
Following #ughoavgfhw's answer, I quickly came up with two categories to accomplish what I needed. Gist: https://gist.github.com/910824
NSEntityDescription:
#interface NSEntityDescription (LocalizedName)
#property (nonatomic, readonly) NSString *localizedName;
#end
#implementation NSEntityDescription (LocalizedName)
#dynamic localizedName;
- (NSString *)localizedName {
static NSString *const localizedNameKeyFormat = #"Entity/%#";
NSString *localizedNameKey = [NSString stringWithFormat:localizedNameKeyFormat, [self name]];
NSString *localizedName = [[[self managedObjectModel] localizationDictionary] objectForKey:localizedNameKey];
if (localizedName) {
return localizedName;
}
return [self name];
}
#end
NSPropertyDescription:
#interface NSPropertyDescription (LocalizedName)
#property (nonatomic, readonly) NSString *localizedName;
#end
#implementation NSPropertyDescription (LocalizedName)
#dynamic localizedName;
- (NSString *)localizedName {
static NSArray *localizedNameKeyFormats = nil;
if (!localizedNameKeyFormats) {
localizedNameKeyFormats = [[NSArray alloc] initWithObjects:#"Property/%#/Entity/%#", #"Property/%#", nil];
}
for (NSString *localizedNameKeyFormat in localizedNameKeyFormats) {
NSString *localizedNameKey = [NSString stringWithFormat:localizedNameKeyFormat, [self name], [[self entity] name]];
NSString *localizedName = [[[[self entity] managedObjectModel] localizationDictionary] objectForKey:localizedNameKey];
if (localizedName) {
return localizedName;
}
}
return [self name];
}
#end
There is no direct way to get that information provided by apple, but you could implement it yourself. You just need to add categories to NSEntityDescription, etc. which create the identifier and ask for the localized value from the model, and then treat it as if it were built in.
Here is an example NSEntityDescription implementation. For properties, you would do something similar, but you should use both the entity and property name in case multiple entities have properties with the same name (you may also need to use both the entity and property name as keys in your localization file. I don't know if the model will create them automatically).
#implementation NSEntityDescription (Localization)
- (NSString *)localizedName {
NSString *key = [NSString stringWithFormat:#"Entity/%#", [self name]];
NSDictionary *dictionary = [[self managedObjectModel] localizationDictionary];
NSString *localizedName = [dictionary objectForKey:key];
return (localizedName ? localizedName : [self name]);
}
#end
Here is a reference for the keys used in the localizations.

interesting task for Fast enumeration protocol

How is known to support FE protocol, I must implement method:
– countByEnumeratingWithState:objects:count:
But it method is do not knows what type of object I want enumerate. For example my custom object has two arrays:
NSArray* names - for NSString objects;
NSArray* sites - for NSURL objects;
Now I want enumerate them:
for( NSString* name in myObj )
{
}
and
for( NSURL* url in myObj )
{
}
Can I do that – countByEnumeratingWithState:objects:count: define what kind of objects it must enumerate? (Without using additional class attributes :) )
No. Fast enumeration can only support one type of enumeration per class so you would have to decide which case is more important for you.
However, NSEnumerator also supports fast enumeration. So your class could support 2 different enumerators (let's call them nameEnumerator and urlEnumerator) and the class's users can then use fast enumeration like this:
for (NSString *name in [myObj nameEnumerator]) { ... }
for (NSURL *url in [myObj urlEnumerator]) { ... }
No, type information is not available in that way. I can't think of any mainstream language that would allow return type polymorphism in this way, which is what you're asking for.
Why not simply expose the arrays as properties?
#interface myObj {
NSArray *names;
NSArray *sites;
}
#property(readonly) NSArray *names;
#property(readonly) NSArray *sites;
#end
#implementation myObj
#synthesize names, sites;
#end
Then you can do this:
for (NSString* name in myObj.names) {
}
for (NSURL* sites in myObj.sites) {
}
Yes, you can do that as long as you ensure that only NSStrings are in names and only NSURLs in sites. You only make a cast with "NSString*" telling the compiler that you know that names contains NSString objects. You don't actually enforce that these are only NSString objects. Hope that helped.
for( NSString* name in [myObj names] ) {…}
and
for( NSURL* url in [myObj sites] ) {…}