RestGoatee-Core is a framework which takes raw NSDictionary
and NSXMLParser
objects and convienently converts them to your own domain models.
Supports: iOS 5.0, OS X 10.7, watchOS 1.0, tvOS 9.0; requires ARC
This library's aim is one of simplicity in the common case and extensibility in the general case:
- The act of translating a data source to a domain model is not the place for business logic or key translation.
- The API layer should be able to handle new objects and object properties seemlessly without requiring new deserialization logic. This commit in the example project added an entirely new response object without fanfare.
- Due to JSON and XML having limited types, the deserializer needs to be able to intelligently map to a larger standard family of types.
- CoreData support is usually not done at the outset of a project; this library makes it easier to turn it on with minimal refactoring. CoreData support is implicit, but inactive in projects without it.
- The default mapping behavior should be both generally intuitive (correct 99% of the time) and extensible.
- The default should be the least verbose in terms of complexity and lines of code. You don't specify mappings for objects or properties that are one-to-one, well named, and explicitly typed.
Consider your favorite or most popular model framework:
- Does it require mappings to build simple objects?
- Does it support
NSManagedObject
subclasses? - Does it understand the keys
foo-bar
foo_bar
andfooBar
are likely the same key? - JSON or XML?
- For Cocoapods users add
pod 'RestGoatee-Core'
to your Podfile and runpod install
. - For Carthage users add
github "rdignard08/RestGoatee-Core" "master"
(you may also specify a release tag instead of master). - For manual installation include the top level folder "RestGoatee-Core" in your repository (everything is prefixed).
- Include
#import "RestGoatee-Core.h"
to include all public headers and start using the library.
- Include
@interface BaseObject : NSObject
@property (nonatomic, strong) NSString* stringValue;
@property (nonatomic, strong) NSNumber* numberValue;
@property (nonatomic, assign) double doubleValue;
@end
@interface DerivedObject : BaseObject
@property (nonatomic, strong) NSDate* dateValue;
@property (nonatomic, strong) id rawValue;
@end
DerivedObject* derived = [DerivedObject objectFromDataSource:@{
@"stringValue" : @"aString",
@"numberValue" : @3,
@"doubleValue" : @3.14,
@"dateValue" : @"2016-01-17T16:13:00-0800",
@"rawValue" : [NSNull null]
} inContext:nil];
assert([derived.stringValue isEqual:@"aString"]);
assert([derived.numberValue isEqual:@3]);
assert(derived.doubleValue == 3.14);
assert([derived.dateValue timeIntervalSince1970] == 1453075980.0);
assert(derived.rawValue == [NSNull null]);
Making an object is as simple as that. Supported data sources out of the box are NSDictionary
(for JSON) and RGXMLNode
(for XML), but the protocol is public and you can freely make your own data source.
DerivedObject* derived = [DerivedObject objectFromDataSource:@{ @"stringValue" : @"aString" } inContext:nil];
assert([derived.stringValue isEqual:@"aString"]);
assert(derived.numberValue == nil);
assert(derived.doubleValue == 0.0);
If a value isn't provided it remains the default value. Likewise, if there are keys which aren't used they'll be ignored.
DerivedObject* derived = [DerivedObject objectFromDataSource:@{ @"stringValue" : [NSNull null] } inContext:nil];
assert(derived.stringValue == nil);
The rules are pretty simple, and guarantee you will never break the type system (an NSURL*
property will always have an NSURL
or nil
).
- If the value provided has the same type or a sub type of the property type it gets set to that value.
- As a consequence, properties of type
id
orNSObject*
will receive any value.
- As a consequence, properties of type
- If the value can be converted to the type of the property (
NSNumber
=>NSString
through.stringValue
for example) it gets set to the converted value.- Most rules occur here
NSString
/NSNumber
maps toid
,NSObject
,NSNumber
,NSURL
,NSDate
,Class
, and primitives (int
,double
,char
, etc.)RGXMLNode
maps toNSDictionary
thenNSDictionary
maps toNSDictionary
or anNSObject
subclass etc.
- Most rules occur here
- Otherwise the property remains unset and the value is discarded. You'll receive a runtime warning when this happens.
- The complete set of rules can be inferred from the test suite in
NSObject+RGDeserializationSpec.m
DerivedObject* derived = [DerivedObject objectFromDataSource:@{ @"string_value" : @"aString" } inContext:nil];
assert([derived.stringValue isEqual:@"aString"]);
Not CamelCase? No problem. The implicit mapping will handle all cases where the case insensitive ASCII alphabet and numbers of the keys match.
@implementation DerivedObject
+ (NSDictionary*) overrideKeysForMapping {
return @{ @"super_secret_str" : @"stringValue" };
}
@end
DerivedObject* derived = [DerivedObject objectFromDataSource:@{ @"super_secret_str" : @"aString" } inContext:nil];
assert([derived.stringValue isEqual:@"aString"]);
Providing +overrideKeysForMapping
gives you the flexibility to map a key to the name of the property. Any key not specified goes through the default process so you only need to specify the exceptions.
@implementation DerivedObject
- (BOOL) shouldTransformValue:(id)value forProperty:(NSString*)propertyName {
if ([propertyName isEqual:@"stringValue"]) {
self.stringValue = [value description].uppercaseString;
return NO;
}
return YES;
}
@end
DerivedObject* derived = [DerivedObject objectFromDataSource:@{ @"stringValue" : @"abcd" } inContext:nil];
assert([derived.stringValue isEqual:@"ABCD"]);
You can override -shouldTransformValue:forProperty:
and return NO
whenever you want to take control directly.
DerivedObject* derived = [DerviedObject new];
derived.stringValue = @"aString";
derived.numberValue = @3;
derived.doubleValue = 3.0;
NSDictionary* dictionaryRepresentation = [derived dictionaryRepresentation];
assert([dictionaryRepresentation[@"stringValue"] isEqual:@"aString"]);
assert([dictionaryRepresentation[@"numberValue"] isEqual:@"3"]);
assert([dictionaryRepresentation[@"doubleValue"] isEqual:@"3"]);
-dictionaryRepresentation
returns a dictionary where the keys are the names of the properties and the values are the result of serializing that value. A property of type NSString*
, NSURL*
, NSNumber*
, or a primitive will be a value of NSString*
. NSNull*
values stay the same. NSArray*
, NSDictionary*
, and all other NSObject*
subclasses are output by applying the same rules to their sub objects.
For a working example see https://github.com/rdignard08/RestGoatee
BSD Simplified (2-clause)