iOS Category Inheritance to encapsulate NSJSONSerialization
ios, objective-c, objective-c-category
Solution
In the code:
+ (NSString *)JSONString;
The `+` (Plus sign) indicates a class method. You most likely want an instance method, like so:
- (NSString *)JSONString;
Regarding the methodology of how to implement serialization: This is quite the "aside". The scope of what you are asking is very broad and you have given very few specifics. This is really a function of the scope of your needs and how much time/energy you want to invest, but to weigh-in an opinion:
A category on `NSObject` seems a bit overly-broad to me. I would think that it's unlikely to yield better results than, what seems like a more logical choice, a protocol.
I frankly wish `NSJSONSerialization` had declared a protocol for serializing our own custom model objects, similar to `NSCoding`, but they didn't. That's still generally the route I go when encoding object into `JSON`. A protocol could look something like this:
NSString * const MySerializationClassKey = @"MySerializationClassKey";
@protocol MySerializationProtocol <NSObject>
@required
-(NSDictionary *)dictionarySerialization;
-(id)initFromDictionarySerialization:(NSDictionary *)dictionary;
@end
And then your custom model objects can serialize and de-serialize like this:(Overly simplistic)
@interface MyModel() <MySerializationProtocol>
@property (strong) NSString *name;
@end
@implementation MyModel
NSString * const MyModelNameKey = @"MyModelNameKey";
@synthesize name = _name;
-(NSDictionary *)dictionarySerialization{
// encode object as dictionary
return @{MySerializationClassKey: NSStringFromClass(self.class), MyModelNameKey: _name.copy};
}
-(id)initFromDictionarySerialization:(NSDictionary *)dictionary{
if ((self = [super init])){
_name = [dictionary objectForKey:MyModelNameKey];
}
return self;
}
@end
Now suppose, for the sake of not typing a complete serialization library into an answer, that you only ever had a flat `NSArray` of these model objects. You could create a class that uses `NSJSONSerialization` in cooperation with these methods. To make each call easier, like so:
@interface MySerializer : NSObject
@end
@implementation MySerializer
+(NSData *)jsonDataFromObject:(NSMutableArray *)array{
for (NSUInteger index = 0; index < array.count; index++) {
NSObject *object = [array objectAtIndex:index];
if ([object conformsToProtocol:@protocol(MySerializationProtocol)]){
NSDictionary *dictRep = [(id<MySerializationProtocol>)object dictionarySerialization];
[array replaceObjectAtIndex:index withObject:dictRep];
}
}
return [NSJSONSerialization dataWithJSONObject:array options:0 error:nil];
}
+(NSMutableArray *)objectFromJSONData:(NSData *)data{
NSMutableArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (NSUInteger index = 0; index < array.count; index++) {
NSObject *object = [array objectAtIndex:index];
if ([object isKindOfClass:[NSDictionary class]]){
NSDictionary *dict = (NSDictionary *)object;
NSString *className = [dict objectForKey:MySerializationClassKey];
if (className.length > 0) {
NSObject *deserialized = [[NSClassFromString(className) alloc] initFromDictionarySerialization:dict];
if (deserialized) [array replaceObjectAtIndex:index withObject:deserialized];
}
}
}
return array;
}
@end
Now obviously to get any mileage out of code like this you would have to recursively traverse the possible arrays and dictionaries. But this is my preferred route.
Problem
I am attempting to encapsulate NSJSONSerialization methods in a `Category` on `NSObject` instead of repeating the [de]/serialization throughout the code. .h ``` #import <Foundation/Foundation.h> @interface NSObject (AYIAdditions) + (NSString *)JSONString; + (id)objectFromJSONString; + (id)objectFromJSONData; @end ``` However, I am receiving the error: No visible @interface for 'NSMutableDictionary' declares the selector 'JSONString' `NSMutableDictionary` inherits `NSObject` and therefore should inherit these category methods, right? What am I missing? As an aside, is using categories a good approach (mentioned by Ray Wenderlich). Or should I simply create an custom object with some class methods.