Initial commit

This commit is contained in:
lianxiang
2019-03-07 22:12:14 +08:00
parent 489ea15862
commit 42cc76b80e
281 changed files with 42464 additions and 3 deletions
+284
View File
@@ -0,0 +1,284 @@
//
// JSONModel.h
// JSONModel
//
#import <Foundation/Foundation.h>
#import "JSONModelError.h"
#import "JSONValueTransformer.h"
#import "JSONKeyMapper.h"
/////////////////////////////////////////////////////////////////////////////////////////////
#if TARGET_IPHONE_SIMULATOR
#define JMLog( s, ... ) NSLog( @"[%@:%d] %@", [[NSString stringWithUTF8String:__FILE__] \
lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
#define JMLog( s, ... )
#endif
/////////////////////////////////////////////////////////////////////////////////////////////
DEPRECATED_ATTRIBUTE
@protocol ConvertOnDemand
@end
DEPRECATED_ATTRIBUTE
@protocol Index
@end
#pragma mark - Property Protocols
/**
* Protocol for defining properties in a JSON Model class that should not be considered at all
* neither while importing nor when exporting JSON.
*
* @property (strong, nonatomic) NSString <Ignore> *propertyName;
*
*/
@protocol Ignore
@end
/**
* Protocol for defining optional properties in a JSON Model class. Use like below to define
* model properties that are not required to have values in the JSON input:
*
* @property (strong, nonatomic) NSString <Optional> *propertyName;
*
*/
@protocol Optional
@end
/**
* Make all objects compatible to avoid compiler warnings
*/
@interface NSObject (JSONModelPropertyCompatibility) <Optional, Ignore>
@end
/////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - JSONModel protocol
/**
* A protocol describing an abstract JSONModel class
* JSONModel conforms to this protocol, so it can use itself abstractly
*/
@protocol AbstractJSONModelProtocol <NSCopying, NSCoding>
@required
/**
* All JSONModel classes should implement initWithDictionary:
*
* For most classes the default initWithDictionary: inherited from JSONModel itself
* should suffice, but developers have the option to also overwrite it if needed.
*
* @param dict a dictionary holding JSON objects, to be imported in the model.
* @param err an error or NULL
*/
- (instancetype)initWithDictionary:(NSDictionary *)dict error:(NSError **)err;
/**
* All JSONModel classes should implement initWithData:error:
*
* For most classes the default initWithData: inherited from JSONModel itself
* should suffice, but developers have the option to also overwrite it if needed.
*
* @param data representing a JSON response (usually fetched from web), to be imported in the model.
* @param error an error or NULL
*/
- (instancetype)initWithData:(NSData *)data error:(NSError **)error;
/**
* All JSONModel classes should be able to export themselves as a dictionary of
* JSON compliant objects.
*
* For most classes the inherited from JSONModel default toDictionary implementation
* should suffice.
*
* @return NSDictionary dictionary of JSON compliant objects
* @exception JSONModelTypeNotAllowedException thrown when one of your model's custom class properties
* does not have matching transformer method in an JSONValueTransformer.
*/
- (NSDictionary *)toDictionary;
/**
* Export a model class to a dictionary, including only given properties
*
* @param propertyNames the properties to export; if nil, all properties exported
* @return NSDictionary dictionary of JSON compliant objects
* @exception JSONModelTypeNotAllowedException thrown when one of your model's custom class properties
* does not have matching transformer method in an JSONValueTransformer.
*/
- (NSDictionary *)toDictionaryWithKeys:(NSArray <NSString *> *)propertyNames;
@end
/////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - JSONModel interface
/**
* The JSONModel is an abstract model class, you should not instantiate it directly,
* as it does not have any properties, and therefore cannot serve as a data model.
* Instead you should subclass it, and define the properties you want your data model
* to have as properties of your own class.
*/
@interface JSONModel : NSObject <AbstractJSONModelProtocol, NSSecureCoding>
// deprecated
+ (NSMutableArray *)arrayOfModelsFromDictionaries:(NSArray *)array DEPRECATED_MSG_ATTRIBUTE("use arrayOfModelsFromDictionaries:error:");
+ (void)setGlobalKeyMapper:(JSONKeyMapper *)globalKeyMapper DEPRECATED_MSG_ATTRIBUTE("override +keyMapper in a base model class instead");
+ (NSString *)protocolForArrayProperty:(NSString *)propertyName DEPRECATED_MSG_ATTRIBUTE("use classForCollectionProperty:");
- (void)mergeFromDictionary:(NSDictionary *)dict useKeyMapping:(BOOL)useKeyMapping DEPRECATED_MSG_ATTRIBUTE("use mergeFromDictionary:useKeyMapping:error:");
- (NSString *)indexPropertyName DEPRECATED_ATTRIBUTE;
- (NSComparisonResult)compare:(id)object DEPRECATED_ATTRIBUTE;
/** @name Creating and initializing models */
/**
* Create a new model instance and initialize it with the JSON from a text parameter. The method assumes UTF8 encoded input text.
* @param string JSON text data
* @param err an initialization error or nil
* @exception JSONModelTypeNotAllowedException thrown when unsupported type is found in the incoming JSON,
* or a property type in your model is not supported by JSONValueTransformer and its categories
* @see initWithString:usingEncoding:error: for use of custom text encodings
*/
- (instancetype)initWithString:(NSString *)string error:(JSONModelError **)err;
/**
* Create a new model instance and initialize it with the JSON from a text parameter using the given encoding.
* @param string JSON text data
* @param encoding the text encoding to use when parsing the string (see NSStringEncoding)
* @param err an initialization error or nil
* @exception JSONModelTypeNotAllowedException thrown when unsupported type is found in the incoming JSON,
* or a property type in your model is not supported by JSONValueTransformer and its categories
*/
- (instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError **)err;
/** @name Exporting model contents */
/**
* Export the whole object to a JSON data text string
* @return JSON text describing the data model
*/
- (NSString *)toJSONString;
/**
* Export the whole object to a JSON data text string
* @return JSON text data describing the data model
*/
- (NSData *)toJSONData;
/**
* Export the specified properties of the object to a JSON data text string
* @param propertyNames the properties to export; if nil, all properties exported
* @return JSON text describing the data model
*/
- (NSString *)toJSONStringWithKeys:(NSArray <NSString *> *)propertyNames;
/**
* Export the specified properties of the object to a JSON data text string
* @param propertyNames the properties to export; if nil, all properties exported
* @return JSON text data describing the data model
*/
- (NSData *)toJSONDataWithKeys:(NSArray <NSString *> *)propertyNames;
/** @name Batch methods */
/**
* If you have a list of dictionaries in a JSON feed, you can use this method to create an NSArray
* of model objects. Handy when importing JSON data lists.
* This method will loop over the input list and initialize a data model for every dictionary in the list.
*
* @param array list of dictionaries to be imported as models
* @return list of initialized data model objects
* @exception JSONModelTypeNotAllowedException thrown when unsupported type is found in the incoming JSON,
* or a property type in your model is not supported by JSONValueTransformer and its categories
* @exception JSONModelInvalidDataException thrown when the input data does not include all required keys
* @see arrayOfDictionariesFromModels:
*/
+ (NSMutableArray *)arrayOfModelsFromDictionaries:(NSArray *)array error:(NSError **)err;
+ (NSMutableArray *)arrayOfModelsFromData:(NSData *)data error:(NSError **)err;
+ (NSMutableArray *)arrayOfModelsFromString:(NSString *)string error:(NSError **)err;
+ (NSMutableDictionary *)dictionaryOfModelsFromDictionary:(NSDictionary *)dictionary error:(NSError **)err;
+ (NSMutableDictionary *)dictionaryOfModelsFromData:(NSData *)data error:(NSError **)err;
+ (NSMutableDictionary *)dictionaryOfModelsFromString:(NSString *)string error:(NSError **)err;
/**
* If you have an NSArray of data model objects, this method takes it in and outputs a list of the
* matching dictionaries. This method does the opposite of arrayOfObjectsFromDictionaries:
* @param array list of JSONModel objects
* @return a list of NSDictionary objects
* @exception JSONModelTypeNotAllowedException thrown when unsupported type is found in the incoming JSON,
* or a property type in your model is not supported by JSONValueTransformer and its categories
* @see arrayOfModelsFromDictionaries:
*/
+ (NSMutableArray *)arrayOfDictionariesFromModels:(NSArray *)array;
+ (NSMutableDictionary *)dictionaryOfDictionariesFromModels:(NSDictionary *)dictionary;
/** @name Validation */
/**
* Overwrite the validate method in your own models if you need to perform some custom validation over the model data.
* This method gets called at the very end of the JSONModel initializer, thus the model is in the state that you would
* get it back when initialized. Check the values of any property that needs to be validated and if any invalid values
* are encountered return NO and set the error parameter to an NSError object. If the model is valid return YES.
*
* NB: Only setting the error parameter is not enough to fail the validation, you also need to return a NO value.
*
* @param error a pointer to an NSError object, to pass back an error if needed
* @return a BOOL result, showing whether the model data validates or not. You can use the convenience method
* [JSONModelError errorModelIsInvalid] to set the NSError param if the data fails your custom validation
*/
- (BOOL)validate:(NSError **)error;
/** @name Key mapping */
/**
* Overwrite in your models if your property names don't match your JSON key names.
* Lookup JSONKeyMapper docs for more details.
*/
+ (JSONKeyMapper *)keyMapper;
/**
* Indicates whether the property with the given name is Optional.
* To have a model with all of its properties being Optional just return YES.
* This method returns by default NO, since the default behaviour is to have all properties required.
* @param propertyName the name of the property
* @return a BOOL result indicating whether the property is optional
*/
+ (BOOL)propertyIsOptional:(NSString *)propertyName;
/**
* Indicates whether the property with the given name is Ignored.
* To have a model with all of its properties being Ignored just return YES.
* This method returns by default NO, since the default behaviour is to have all properties required.
* @param propertyName the name of the property
* @return a BOOL result indicating whether the property is ignored
*/
+ (BOOL)propertyIsIgnored:(NSString *)propertyName;
/**
* Indicates the class used for the elements of a collection property.
* Rather than using:
* @property (strong) NSArray <MyType> *things;
* You can implement classForCollectionProperty: and keep your property
* defined like:
* @property (strong) NSArray *things;
* @param propertyName the name of the property
* @return Class the class used to deserialize the elements of the collection
*
* Example in Swift 3.0:
* override static func classForCollectionProperty(propertyName: String) -> AnyClass? {
* switch propertyName {
* case "childModel":
* return ChildModel.self
* default:
* return nil
* }
* }
*/
+ (Class)classForCollectionProperty:(NSString *)propertyName NS_SWIFT_NAME(classForCollectionProperty(propertyName:));
/**
* Merges values from the given dictionary into the model instance.
* @param dict dictionary with values
* @param useKeyMapping if YES the method will use the model's key mapper and the global key mapper, if NO
* it'll just try to match the dictionary keys to the model's properties
*/
- (BOOL)mergeFromDictionary:(NSDictionary *)dict useKeyMapping:(BOOL)useKeyMapping error:(NSError **)error;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,48 @@
//
// JSONModelClassProperty.h
// JSONModel
//
#import <Foundation/Foundation.h>
/**
* **You do not need to instantiate this class yourself.** This class is used internally by JSONModel
* to inspect the declared properties of your model class.
*
* Class to contain the information, representing a class property
* It features the property's name, type, whether it's a required property,
* and (optionally) the class protocol
*/
@interface JSONModelClassProperty : NSObject
// deprecated
@property (assign, nonatomic) BOOL isIndex DEPRECATED_ATTRIBUTE;
/** The name of the declared property (not the ivar name) */
@property (copy, nonatomic) NSString *name;
/** A property class type */
@property (assign, nonatomic) Class type;
/** Struct name if a struct */
@property (strong, nonatomic) NSString *structName;
/** The name of the protocol the property conforms to (or nil) */
@property (copy, nonatomic) NSString *protocol;
/** If YES, it can be missing in the input data, and the input would be still valid */
@property (assign, nonatomic) BOOL isOptional;
/** If YES - don't call any transformers on this property's value */
@property (assign, nonatomic) BOOL isStandardJSONType;
/** If YES - create a mutable object for the value of the property */
@property (assign, nonatomic) BOOL isMutable;
/** a custom getter for this property, found in the owning model */
@property (assign, nonatomic) SEL customGetter;
/** custom setters for this property, found in the owning model */
@property (strong, nonatomic) NSMutableDictionary *customSetters;
@end
@@ -0,0 +1,53 @@
//
// JSONModelClassProperty.m
// JSONModel
//
#import "JSONModelClassProperty.h"
@implementation JSONModelClassProperty
-(NSString*)description
{
//build the properties string for the current class property
NSMutableArray* properties = [NSMutableArray arrayWithCapacity:8];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
if (self.isIndex) [properties addObject:@"Index"];
#pragma GCC diagnostic pop
if (self.isOptional) [properties addObject:@"Optional"];
if (self.isMutable) [properties addObject:@"Mutable"];
if (self.isStandardJSONType) [properties addObject:@"Standard JSON type"];
if (self.customGetter) [properties addObject:[NSString stringWithFormat: @"Getter = %@", NSStringFromSelector(self.customGetter)]];
if (self.customSetters)
{
NSMutableArray *setters = [NSMutableArray array];
for (id obj in self.customSetters.allValues)
{
SEL selector;
[obj getValue:&selector];
[setters addObject:NSStringFromSelector(selector)];
}
[properties addObject:[NSString stringWithFormat: @"Setters = [%@]", [setters componentsJoinedByString:@", "]]];
}
NSString* propertiesString = @"";
if (properties.count>0) {
propertiesString = [NSString stringWithFormat:@"(%@)", [properties componentsJoinedByString:@", "]];
}
//return the name, type and additional properties
return [NSString stringWithFormat:@"@property %@%@ %@ %@",
self.type?[NSString stringWithFormat:@"%@*",self.type]:(self.structName?self.structName:@"primitive"),
self.protocol?[NSString stringWithFormat:@"<%@>", self.protocol]:@"",
self.name,
propertiesString
];
}
@end
+103
View File
@@ -0,0 +1,103 @@
//
// JSONModelError.h
// JSONModel
//
#import <Foundation/Foundation.h>
/////////////////////////////////////////////////////////////////////////////////////////////
typedef NS_ENUM(int, kJSONModelErrorTypes)
{
kJSONModelErrorInvalidData = 1,
kJSONModelErrorBadResponse = 2,
kJSONModelErrorBadJSON = 3,
kJSONModelErrorModelIsInvalid = 4,
kJSONModelErrorNilInput = 5
};
/////////////////////////////////////////////////////////////////////////////////////////////
/** The domain name used for the JSONModelError instances */
extern NSString *const JSONModelErrorDomain;
/**
* If the model JSON input misses keys that are required, check the
* userInfo dictionary of the JSONModelError instance you get back -
* under the kJSONModelMissingKeys key you will find a list of the
* names of the missing keys.
*/
extern NSString *const kJSONModelMissingKeys;
/**
* If JSON input has a different type than expected by the model, check the
* userInfo dictionary of the JSONModelError instance you get back -
* under the kJSONModelTypeMismatch key you will find a description
* of the mismatched types.
*/
extern NSString *const kJSONModelTypeMismatch;
/**
* If an error occurs in a nested model, check the userInfo dictionary of
* the JSONModelError instance you get back - under the kJSONModelKeyPath
* key you will find key-path at which the error occurred.
*/
extern NSString *const kJSONModelKeyPath;
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* Custom NSError subclass with shortcut methods for creating
* the common JSONModel errors
*/
@interface JSONModelError : NSError
@property (strong, nonatomic) NSHTTPURLResponse *httpResponse;
@property (strong, nonatomic) NSData *responseData;
/**
* Creates a JSONModelError instance with code kJSONModelErrorInvalidData = 1
*/
+ (id)errorInvalidDataWithMessage:(NSString *)message;
/**
* Creates a JSONModelError instance with code kJSONModelErrorInvalidData = 1
* @param keys a set of field names that were required, but not found in the input
*/
+ (id)errorInvalidDataWithMissingKeys:(NSSet *)keys;
/**
* Creates a JSONModelError instance with code kJSONModelErrorInvalidData = 1
* @param mismatchDescription description of the type mismatch that was encountered.
*/
+ (id)errorInvalidDataWithTypeMismatch:(NSString *)mismatchDescription;
/**
* Creates a JSONModelError instance with code kJSONModelErrorBadResponse = 2
*/
+ (id)errorBadResponse;
/**
* Creates a JSONModelError instance with code kJSONModelErrorBadJSON = 3
*/
+ (id)errorBadJSON;
/**
* Creates a JSONModelError instance with code kJSONModelErrorModelIsInvalid = 4
*/
+ (id)errorModelIsInvalid;
/**
* Creates a JSONModelError instance with code kJSONModelErrorNilInput = 5
*/
+ (id)errorInputIsNil;
/**
* Creates a new JSONModelError with the same values plus information about the key-path of the error.
* Properties in the new error object are the same as those from the receiver,
* except that a new key kJSONModelKeyPath is added to the userInfo dictionary.
* This key contains the component string parameter. If the key is already present
* then the new error object has the component string prepended to the existing value.
*/
- (instancetype)errorByPrependingKeyPathComponent:(NSString *)component;
/////////////////////////////////////////////////////////////////////////////////////////////
@end
+82
View File
@@ -0,0 +1,82 @@
//
// JSONModelError.m
// JSONModel
//
#import "JSONModelError.h"
NSString* const JSONModelErrorDomain = @"JSONModelErrorDomain";
NSString* const kJSONModelMissingKeys = @"kJSONModelMissingKeys";
NSString* const kJSONModelTypeMismatch = @"kJSONModelTypeMismatch";
NSString* const kJSONModelKeyPath = @"kJSONModelKeyPath";
@implementation JSONModelError
+(id)errorInvalidDataWithMessage:(NSString*)message
{
message = [NSString stringWithFormat:@"Invalid JSON data: %@", message];
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorInvalidData
userInfo:@{NSLocalizedDescriptionKey:message}];
}
+(id)errorInvalidDataWithMissingKeys:(NSSet *)keys
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorInvalidData
userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON data. Required JSON keys are missing from the input. Check the error user information.",kJSONModelMissingKeys:[keys allObjects]}];
}
+(id)errorInvalidDataWithTypeMismatch:(NSString*)mismatchDescription
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorInvalidData
userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON data. The JSON type mismatches the expected type. Check the error user information.",kJSONModelTypeMismatch:mismatchDescription}];
}
+(id)errorBadResponse
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorBadResponse
userInfo:@{NSLocalizedDescriptionKey:@"Bad network response. Probably the JSON URL is unreachable."}];
}
+(id)errorBadJSON
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorBadJSON
userInfo:@{NSLocalizedDescriptionKey:@"Malformed JSON. Check the JSONModel data input."}];
}
+(id)errorModelIsInvalid
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorModelIsInvalid
userInfo:@{NSLocalizedDescriptionKey:@"Model does not validate. The custom validation for the input data failed."}];
}
+(id)errorInputIsNil
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorNilInput
userInfo:@{NSLocalizedDescriptionKey:@"Initializing model with nil input object."}];
}
- (instancetype)errorByPrependingKeyPathComponent:(NSString*)component
{
// Create a mutable copy of the user info so that we can add to it and update it
NSMutableDictionary* userInfo = [self.userInfo mutableCopy];
// Create or update the key-path
NSString* existingPath = userInfo[kJSONModelKeyPath];
NSString* separator = [existingPath hasPrefix:@"["] ? @"" : @".";
NSString* updatedPath = (existingPath == nil) ? component : [component stringByAppendingFormat:@"%@%@", separator, existingPath];
userInfo[kJSONModelKeyPath] = updatedPath;
// Create the new error
return [JSONModelError errorWithDomain:self.domain
code:self.code
userInfo:[NSDictionary dictionaryWithDictionary:userInfo]];
}
@end
+19
View File
@@ -0,0 +1,19 @@
//
// JSONModelLib.h
// JSONModel
//
#import <Foundation/Foundation.h>
// core
#import "JSONModel.h"
#import "JSONModelError.h"
// transformations
#import "JSONValueTransformer.h"
#import "JSONKeyMapper.h"
// networking (deprecated)
#import "JSONHTTPClient.h"
#import "JSONModel+networking.h"
#import "JSONAPI.h"
+19
View File
@@ -0,0 +1,19 @@
//
// JSONAPI.h
// JSONModel
//
#import <Foundation/Foundation.h>
#import "JSONHTTPClient.h"
DEPRECATED_ATTRIBUTE
@interface JSONAPI : NSObject
+ (void)setAPIBaseURLWithString:(NSString *)base DEPRECATED_ATTRIBUTE;
+ (void)setContentType:(NSString *)ctype DEPRECATED_ATTRIBUTE;
+ (void)getWithPath:(NSString *)path andParams:(NSDictionary *)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postWithPath:(NSString *)path andParams:(NSDictionary *)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)rpcWithMethodName:(NSString *)method andArguments:(NSArray *)args completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)rpc2WithMethodName:(NSString *)method andParams:(id)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
@end
+145
View File
@@ -0,0 +1,145 @@
//
// JSONAPI.m
// JSONModel
//
#import "JSONAPI.h"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
#pragma mark - helper error model class
@interface JSONAPIRPCErrorModel: JSONModel
@property (assign, nonatomic) int code;
@property (strong, nonatomic) NSString* message;
@property (strong, nonatomic) id<Optional> data;
@end
#pragma mark - static variables
static JSONAPI* sharedInstance = nil;
static long jsonRpcId = 0;
#pragma mark - JSONAPI() private interface
@interface JSONAPI ()
@property (strong, nonatomic) NSString* baseURLString;
@end
#pragma mark - JSONAPI implementation
@implementation JSONAPI
#pragma mark - initialize
+(void)initialize
{
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedInstance = [[JSONAPI alloc] init];
});
}
#pragma mark - api config methods
+(void)setAPIBaseURLWithString:(NSString*)base
{
sharedInstance.baseURLString = base;
}
+(void)setContentType:(NSString*)ctype
{
[JSONHTTPClient setRequestContentType: ctype];
}
#pragma mark - GET methods
+(void)getWithPath:(NSString*)path andParams:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
{
NSString* fullURL = [NSString stringWithFormat:@"%@%@", sharedInstance.baseURLString, path];
[JSONHTTPClient getJSONFromURLWithString: fullURL params:params completion:^(NSDictionary *json, JSONModelError *e) {
completeBlock(json, e);
}];
}
#pragma mark - POST methods
+(void)postWithPath:(NSString*)path andParams:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
{
NSString* fullURL = [NSString stringWithFormat:@"%@%@", sharedInstance.baseURLString, path];
[JSONHTTPClient postJSONFromURLWithString: fullURL params:params completion:^(NSDictionary *json, JSONModelError *e) {
completeBlock(json, e);
}];
}
#pragma mark - RPC methods
+(void)__rpcRequestWithObject:(id)jsonObject completion:(JSONObjectBlock)completeBlock
{
NSData* jsonRequestData = [NSJSONSerialization dataWithJSONObject:jsonObject
options:kNilOptions
error:nil];
NSString* jsonRequestString = [[NSString alloc] initWithData:jsonRequestData encoding: NSUTF8StringEncoding];
NSAssert(sharedInstance.baseURLString, @"API base URL not set");
[JSONHTTPClient postJSONFromURLWithString: sharedInstance.baseURLString
bodyString: jsonRequestString
completion:^(NSDictionary *json, JSONModelError* e) {
if (completeBlock) {
//handle the rpc response
NSDictionary* result = json[@"result"];
if (!result) {
JSONAPIRPCErrorModel* error = [[JSONAPIRPCErrorModel alloc] initWithDictionary:json[@"error"] error:nil];
if (error) {
//custom server error
if (!error.message) error.message = @"Generic json rpc error";
e = [JSONModelError errorWithDomain:JSONModelErrorDomain
code:error.code
userInfo: @{ NSLocalizedDescriptionKey : error.message}];
} else {
//generic error
e = [JSONModelError errorBadResponse];
}
}
//invoke the callback
completeBlock(result, e);
}
}];
}
+(void)rpcWithMethodName:(NSString*)method andArguments:(NSArray*)args completion:(JSONObjectBlock)completeBlock
{
NSAssert(method, @"No method specified");
if (!args) args = @[];
[self __rpcRequestWithObject:@{
//rpc 1.0
@"id": @(++jsonRpcId),
@"params": args,
@"method": method
} completion:completeBlock];
}
+(void)rpc2WithMethodName:(NSString*)method andParams:(id)params completion:(JSONObjectBlock)completeBlock
{
NSAssert(method, @"No method specified");
if (!params) params = @[];
[self __rpcRequestWithObject:@{
//rpc 2.0
@"jsonrpc": @"2.0",
@"id": @(++jsonRpcId),
@"params": params,
@"method": method
} completion:completeBlock];
}
@end
#pragma mark - helper rpc error model class implementation
@implementation JSONAPIRPCErrorModel
@end
@@ -0,0 +1,33 @@
//
// JSONModelHTTPClient.h
// JSONModel
//
#import "JSONModel.h"
extern NSString *const kHTTPMethodGET DEPRECATED_ATTRIBUTE;
extern NSString *const kHTTPMethodPOST DEPRECATED_ATTRIBUTE;
extern NSString *const kContentTypeAutomatic DEPRECATED_ATTRIBUTE;
extern NSString *const kContentTypeJSON DEPRECATED_ATTRIBUTE;
extern NSString *const kContentTypeWWWEncoded DEPRECATED_ATTRIBUTE;
typedef void (^JSONObjectBlock)(id json, JSONModelError *err) DEPRECATED_ATTRIBUTE;
DEPRECATED_ATTRIBUTE
@interface JSONHTTPClient : NSObject
+ (NSMutableDictionary *)requestHeaders DEPRECATED_ATTRIBUTE;
+ (void)setDefaultTextEncoding:(NSStringEncoding)encoding DEPRECATED_ATTRIBUTE;
+ (void)setCachingPolicy:(NSURLRequestCachePolicy)policy DEPRECATED_ATTRIBUTE;
+ (void)setTimeoutInSeconds:(int)seconds DEPRECATED_ATTRIBUTE;
+ (void)setRequestContentType:(NSString *)contentTypeString DEPRECATED_ATTRIBUTE;
+ (void)getJSONFromURLWithString:(NSString *)urlString completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)getJSONFromURLWithString:(NSString *)urlString params:(NSDictionary *)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)JSONFromURLWithString:(NSString *)urlString method:(NSString *)method params:(NSDictionary *)params orBodyString:(NSString *)bodyString completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)JSONFromURLWithString:(NSString *)urlString method:(NSString *)method params:(NSDictionary *)params orBodyString:(NSString *)bodyString headers:(NSDictionary *)headers completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)JSONFromURLWithString:(NSString *)urlString method:(NSString *)method params:(NSDictionary *)params orBodyData:(NSData *)bodyData headers:(NSDictionary *)headers completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postJSONFromURLWithString:(NSString *)urlString params:(NSDictionary *)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postJSONFromURLWithString:(NSString *)urlString bodyString:(NSString *)bodyString completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postJSONFromURLWithString:(NSString *)urlString bodyData:(NSData *)bodyData completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
@end
@@ -0,0 +1,361 @@
//
// JSONModelHTTPClient.m
// JSONModel
//
#import "JSONHTTPClient.h"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
typedef void (^RequestResultBlock)(NSData *data, JSONModelError *error);
#pragma mark - constants
NSString* const kHTTPMethodGET = @"GET";
NSString* const kHTTPMethodPOST = @"POST";
NSString* const kContentTypeAutomatic = @"jsonmodel/automatic";
NSString* const kContentTypeJSON = @"application/json";
NSString* const kContentTypeWWWEncoded = @"application/x-www-form-urlencoded";
#pragma mark - static variables
/**
* Defaults for HTTP requests
*/
static NSStringEncoding defaultTextEncoding = NSUTF8StringEncoding;
static NSURLRequestCachePolicy defaultCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
static int defaultTimeoutInSeconds = 60;
/**
* Custom HTTP headers to send over with *each* request
*/
static NSMutableDictionary* requestHeaders = nil;
/**
* Default request content type
*/
static NSString* requestContentType = nil;
#pragma mark - implementation
@implementation JSONHTTPClient
#pragma mark - initialization
+(void)initialize
{
static dispatch_once_t once;
dispatch_once(&once, ^{
requestHeaders = [NSMutableDictionary dictionary];
requestContentType = kContentTypeAutomatic;
});
}
#pragma mark - configuration methods
+(NSMutableDictionary*)requestHeaders
{
return requestHeaders;
}
+(void)setDefaultTextEncoding:(NSStringEncoding)encoding
{
defaultTextEncoding = encoding;
}
+(void)setCachingPolicy:(NSURLRequestCachePolicy)policy
{
defaultCachePolicy = policy;
}
+(void)setTimeoutInSeconds:(int)seconds
{
defaultTimeoutInSeconds = seconds;
}
+(void)setRequestContentType:(NSString*)contentTypeString
{
requestContentType = contentTypeString;
}
#pragma mark - helper methods
+(NSString*)contentTypeForRequestString:(NSString*)requestString
{
//fetch the charset name from the default string encoding
NSString* contentType = requestContentType;
if (requestString.length>0 && [contentType isEqualToString:kContentTypeAutomatic]) {
//check for "eventual" JSON array or dictionary
NSString* firstAndLastChar = [NSString stringWithFormat:@"%@%@",
[requestString substringToIndex:1],
[requestString substringFromIndex: requestString.length -1]
];
if ([firstAndLastChar isEqualToString:@"{}"] || [firstAndLastChar isEqualToString:@"[]"]) {
//guessing for a JSON request
contentType = kContentTypeJSON;
} else {
//fallback to www form encoded params
contentType = kContentTypeWWWEncoded;
}
}
//type is set, just add charset
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
return [NSString stringWithFormat:@"%@; charset=%@", contentType, charset];
}
+(NSString*)urlEncode:(id<NSObject>)value
{
//make sure param is a string
if ([value isKindOfClass:[NSNumber class]]) {
value = [(NSNumber*)value stringValue];
}
NSAssert([value isKindOfClass:[NSString class]], @"request parameters can be only of NSString or NSNumber classes. '%@' is of class %@.", value, [value class]);
NSString *str = (NSString *)value;
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
return [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
#else
return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(
NULL,
(__bridge CFStringRef)str,
NULL,
(CFStringRef)@"!*'();:@&=+$,/?%#[]",
kCFStringEncodingUTF8));
#endif
}
#pragma mark - networking worker methods
+(void)requestDataFromURL:(NSURL*)url method:(NSString*)method requestBody:(NSData*)bodyData headers:(NSDictionary*)headers handler:(RequestResultBlock)handler
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: url
cachePolicy: defaultCachePolicy
timeoutInterval: defaultTimeoutInSeconds];
[request setHTTPMethod:method];
if ([requestContentType isEqualToString:kContentTypeAutomatic]) {
//automatic content type
if (bodyData) {
NSString *bodyString = [[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding];
[request setValue: [self contentTypeForRequestString: bodyString] forHTTPHeaderField:@"Content-type"];
}
} else {
//user set content type
[request setValue: requestContentType forHTTPHeaderField:@"Content-type"];
}
//add all the custom headers defined
for (NSString* key in [requestHeaders allKeys]) {
[request setValue:requestHeaders[key] forHTTPHeaderField:key];
}
//add the custom headers
for (NSString* key in [headers allKeys]) {
[request setValue:headers[key] forHTTPHeaderField:key];
}
if (bodyData) {
[request setHTTPBody: bodyData];
[request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)bodyData.length] forHTTPHeaderField:@"Content-Length"];
}
void (^completionHandler)(NSData *, NSURLResponse *, NSError *) = ^(NSData *data, NSURLResponse *origResponse, NSError *origError) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)origResponse;
JSONModelError *error = nil;
//convert an NSError to a JSONModelError
if (origError) {
error = [JSONModelError errorWithDomain:origError.domain code:origError.code userInfo:origError.userInfo];
}
//special case for http error code 401
if (error.code == NSURLErrorUserCancelledAuthentication) {
response = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:401 HTTPVersion:@"HTTP/1.1" headerFields:@{}];
}
//if not OK status set the err to a JSONModelError instance
if (!error && (response.statusCode >= 300 || response.statusCode < 200)) {
error = [JSONModelError errorBadResponse];
}
//if there was an error, assign the response to the JSONModel instance
if (error) {
error.httpResponse = [response copy];
}
//empty respone, return nil instead
if (!data.length) {
data = nil;
}
handler(data, error);
};
//fire the request
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:completionHandler];
[task resume];
#else
NSOperationQueue *queue = [NSOperationQueue new];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
completionHandler(data, response, error);
}];
#endif
}
+(void)requestDataFromURL:(NSURL*)url method:(NSString*)method params:(NSDictionary*)params headers:(NSDictionary*)headers handler:(RequestResultBlock)handler
{
//create the request body
NSMutableString* paramsString = nil;
if (params) {
//build a simple url encoded param string
paramsString = [NSMutableString stringWithString:@""];
for (NSString* key in [[params allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[paramsString appendFormat:@"%@=%@&", key, [self urlEncode:params[key]] ];
}
if ([paramsString hasSuffix:@"&"]) {
paramsString = [[NSMutableString alloc] initWithString: [paramsString substringToIndex: paramsString.length-1]];
}
}
//set the request params
if ([method isEqualToString:kHTTPMethodGET] && params) {
//add GET params to the query string
url = [NSURL URLWithString:[NSString stringWithFormat: @"%@%@%@",
[url absoluteString],
[url query] ? @"&" : @"?",
paramsString
]];
}
//call the more general synq request method
[self requestDataFromURL: url
method: method
requestBody: [method isEqualToString:kHTTPMethodPOST]?[paramsString dataUsingEncoding:NSUTF8StringEncoding]:nil
headers: headers
handler:handler];
}
#pragma mark - Async network request
+(void)JSONFromURLWithString:(NSString*)urlString method:(NSString*)method params:(NSDictionary*)params orBodyString:(NSString*)bodyString completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString
method:method
params:params
orBodyString:bodyString
headers:nil
completion:completeBlock];
}
+(void)JSONFromURLWithString:(NSString *)urlString method:(NSString *)method params:(NSDictionary *)params orBodyString:(NSString *)bodyString headers:(NSDictionary *)headers completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString
method:method
params:params
orBodyData:[bodyString dataUsingEncoding:NSUTF8StringEncoding]
headers:headers
completion:completeBlock];
}
+(void)JSONFromURLWithString:(NSString*)urlString method:(NSString*)method params:(NSDictionary *)params orBodyData:(NSData*)bodyData headers:(NSDictionary*)headers completion:(JSONObjectBlock)completeBlock
{
RequestResultBlock handler = ^(NSData *responseData, JSONModelError *error) {
id jsonObject = nil;
//step 3: if there's no response so far, return a basic error
if (!responseData && !error) {
//check for false response, but no network error
error = [JSONModelError errorBadResponse];
}
//step 4: if there's a response at this and no errors, convert to object
if (error==nil) {
// Note: it is possible to have a valid response with empty response data (204 No Content).
// So only create the JSON object if there is some response data.
if(responseData.length > 0)
{
//convert to an object
jsonObject = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
}
}
//step 4.5: cover an edge case in which meaningful content is return along an error HTTP status code
else if (error && responseData && jsonObject==nil) {
//try to get the JSON object, while preserving the original error object
jsonObject = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:nil];
//keep responseData just in case it contains error information
error.responseData = responseData;
}
//step 5: invoke the complete block
dispatch_async(dispatch_get_main_queue(), ^{
if (completeBlock) {
completeBlock(jsonObject, error);
}
});
};
NSURL *url = [NSURL URLWithString:urlString];
if (bodyData) {
[self requestDataFromURL:url method:method requestBody:bodyData headers:headers handler:handler];
} else {
[self requestDataFromURL:url method:method params:params headers:headers handler:handler];
}
}
#pragma mark - request aliases
+(void)getJSONFromURLWithString:(NSString*)urlString completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodGET
params:nil
orBodyString:nil completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
+(void)getJSONFromURLWithString:(NSString*)urlString params:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodGET
params:params
orBodyString:nil completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
+(void)postJSONFromURLWithString:(NSString*)urlString params:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodPOST
params:params
orBodyString:nil completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
+(void)postJSONFromURLWithString:(NSString*)urlString bodyString:(NSString*)bodyString completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodPOST
params:nil
orBodyString:bodyString completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
+(void)postJSONFromURLWithString:(NSString*)urlString bodyData:(NSData*)bodyData completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodPOST
params:nil
orBodyString:[[NSString alloc] initWithData:bodyData encoding:defaultTextEncoding]
completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
@end
@@ -0,0 +1,18 @@
//
// JSONModel+networking.h
// JSONModel
//
#import "JSONModel.h"
#import "JSONHTTPClient.h"
typedef void (^JSONModelBlock)(id model, JSONModelError *err) DEPRECATED_ATTRIBUTE;
@interface JSONModel (Networking)
@property (assign, nonatomic) BOOL isLoading DEPRECATED_ATTRIBUTE;
- (instancetype)initFromURLWithString:(NSString *)urlString completion:(JSONModelBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)getModelFromURLWithString:(NSString *)urlString completion:(JSONModelBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postModel:(JSONModel *)post toURLWithString:(NSString *)urlString completion:(JSONModelBlock)completeBlock DEPRECATED_ATTRIBUTE;
@end
@@ -0,0 +1,101 @@
//
// JSONModel+networking.m
// JSONModel
//
#import "JSONModel+networking.h"
#import "JSONHTTPClient.h"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
BOOL _isLoading;
@implementation JSONModel(Networking)
@dynamic isLoading;
-(BOOL)isLoading
{
return _isLoading;
}
-(void)setIsLoading:(BOOL)isLoading
{
_isLoading = isLoading;
}
-(instancetype)initFromURLWithString:(NSString *)urlString completion:(JSONModelBlock)completeBlock
{
id placeholder = [super init];
__block id blockSelf = self;
if (placeholder) {
//initialization
self.isLoading = YES;
[JSONHTTPClient getJSONFromURLWithString:urlString
completion:^(NSDictionary *json, JSONModelError* e) {
JSONModelError* initError = nil;
blockSelf = [self initWithDictionary:json error:&initError];
if (completeBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
completeBlock(blockSelf, e?e:initError );
});
}
self.isLoading = NO;
}];
}
return placeholder;
}
+ (void)getModelFromURLWithString:(NSString*)urlString completion:(JSONModelBlock)completeBlock
{
[JSONHTTPClient getJSONFromURLWithString:urlString
completion:^(NSDictionary* jsonDict, JSONModelError* err)
{
JSONModel* model = nil;
if(err == nil)
{
model = [[self alloc] initWithDictionary:jsonDict error:&err];
}
if(completeBlock != nil)
{
dispatch_async(dispatch_get_main_queue(), ^
{
completeBlock(model, err);
});
}
}];
}
+ (void)postModel:(JSONModel*)post toURLWithString:(NSString*)urlString completion:(JSONModelBlock)completeBlock
{
[JSONHTTPClient postJSONFromURLWithString:urlString
bodyString:[post toJSONString]
completion:^(NSDictionary* jsonDict, JSONModelError* err)
{
JSONModel* model = nil;
if(err == nil)
{
model = [[self alloc] initWithDictionary:jsonDict error:&err];
}
if(completeBlock != nil)
{
dispatch_async(dispatch_get_main_queue(), ^
{
completeBlock(model, err);
});
}
}];
}
@end
@@ -0,0 +1,96 @@
//
// JSONKeyMapper.h
// JSONModel
//
#import <Foundation/Foundation.h>
typedef NSString *(^JSONModelKeyMapBlock)(NSString *keyName);
/**
* **You won't need to create or store instances of this class yourself.** If you want your model
* to have different property names than the JSON feed keys, look below on how to
* make your model use a key mapper.
*
* For example if you consume JSON from twitter
* you get back underscore_case style key names. For example:
*
* <pre>"profile_sidebar_border_color": "0094C2",
* "profile_background_tile": false,</pre>
*
* To comply with Obj-C accepted camelCase property naming for your classes,
* you need to provide mapping between JSON keys and ObjC property names.
*
* In your model overwrite the + (JSONKeyMapper *)keyMapper method and provide a JSONKeyMapper
* instance to convert the key names for your model.
*
* If you need custom mapping it's as easy as:
* <pre>
* + (JSONKeyMapper *)keyMapper {
* &nbsp; return [[JSONKeyMapper&nbsp;alloc]&nbsp;initWithDictionary:@{@"crazy_JSON_name":@"myCamelCaseName"}];
* }
* </pre>
* In case you want to handle underscore_case, **use the predefined key mapper**, like so:
* <pre>
* + (JSONKeyMapper *)keyMapper {
* &nbsp; return [JSONKeyMapper&nbsp;mapperFromUnderscoreCaseToCamelCase];
* }
* </pre>
*/
@interface JSONKeyMapper : NSObject
// deprecated
@property (readonly, nonatomic) JSONModelKeyMapBlock JSONToModelKeyBlock DEPRECATED_ATTRIBUTE;
- (NSString *)convertValue:(NSString *)value isImportingToModel:(BOOL)importing DEPRECATED_MSG_ATTRIBUTE("use convertValue:");
- (instancetype)initWithDictionary:(NSDictionary *)map DEPRECATED_MSG_ATTRIBUTE("use initWithModelToJSONDictionary:");
- (instancetype)initWithJSONToModelBlock:(JSONModelKeyMapBlock)toModel modelToJSONBlock:(JSONModelKeyMapBlock)toJSON DEPRECATED_MSG_ATTRIBUTE("use initWithModelToJSONBlock:");
+ (instancetype)mapper:(JSONKeyMapper *)baseKeyMapper withExceptions:(NSDictionary *)exceptions DEPRECATED_MSG_ATTRIBUTE("use baseMapper:withModelToJSONExceptions:");
+ (instancetype)mapperFromUnderscoreCaseToCamelCase DEPRECATED_MSG_ATTRIBUTE("use mapperForSnakeCase:");
+ (instancetype)mapperFromUpperCaseToLowerCase DEPRECATED_ATTRIBUTE;
/** @name Name converters */
/** Block, which takes in a property name and converts it to the corresponding JSON key name */
@property (readonly, nonatomic) JSONModelKeyMapBlock modelToJSONKeyBlock;
/** Combined converter method
* @param value the source name
* @return JSONKeyMapper instance
*/
- (NSString *)convertValue:(NSString *)value;
/** @name Creating a key mapper */
/**
* Creates a JSONKeyMapper instance, based on the block you provide this initializer.
* The parameter takes in a JSONModelKeyMapBlock block:
* <pre>NSString *(^JSONModelKeyMapBlock)(NSString *keyName)</pre>
* The block takes in a string and returns the transformed (if at all) string.
* @param toJSON transforms your model property name to a JSON key
*/
- (instancetype)initWithModelToJSONBlock:(JSONModelKeyMapBlock)toJSON;
/**
* Creates a JSONKeyMapper instance, based on the mapping you provide.
* Use your JSONModel property names as keys, and the JSON key names as values.
* @param toJSON map dictionary, in the format: <pre>@{@"myCamelCaseName":@"crazy_JSON_name"}</pre>
* @return JSONKeyMapper instance
*/
- (instancetype)initWithModelToJSONDictionary:(NSDictionary <NSString *, NSString *> *)toJSON;
/**
* Given a camelCase model property, this mapper finds JSON keys using the snake_case equivalent.
*/
+ (instancetype)mapperForSnakeCase;
/**
* Given a camelCase model property, this mapper finds JSON keys using the TitleCase equivalent.
*/
+ (instancetype)mapperForTitleCase;
/**
* Creates a JSONKeyMapper based on a built-in JSONKeyMapper, with specific exceptions.
* Use your JSONModel property names as keys, and the JSON key names as values.
*/
+ (instancetype)baseMapper:(JSONKeyMapper *)baseKeyMapper withModelToJSONExceptions:(NSDictionary *)toJSON;
@end
@@ -0,0 +1,146 @@
//
// JSONKeyMapper.m
// JSONModel
//
#import "JSONKeyMapper.h"
@implementation JSONKeyMapper
- (instancetype)initWithJSONToModelBlock:(JSONModelKeyMapBlock)toModel modelToJSONBlock:(JSONModelKeyMapBlock)toJSON
{
return [self initWithModelToJSONBlock:toJSON];
}
- (instancetype)initWithModelToJSONBlock:(JSONModelKeyMapBlock)toJSON
{
if (!(self = [self init]))
return nil;
_modelToJSONKeyBlock = toJSON;
return self;
}
- (instancetype)initWithDictionary:(NSDictionary *)map
{
NSDictionary *toJSON = [JSONKeyMapper swapKeysAndValuesInDictionary:map];
return [self initWithModelToJSONDictionary:toJSON];
}
- (instancetype)initWithModelToJSONDictionary:(NSDictionary <NSString *, NSString *> *)toJSON
{
if (!(self = [super init]))
return nil;
_modelToJSONKeyBlock = ^NSString *(NSString *keyName)
{
return [toJSON valueForKeyPath:keyName] ?: keyName;
};
return self;
}
- (JSONModelKeyMapBlock)JSONToModelKeyBlock
{
return nil;
}
+ (NSDictionary *)swapKeysAndValuesInDictionary:(NSDictionary *)dictionary
{
NSArray *keys = dictionary.allKeys;
NSArray *values = [dictionary objectsForKeys:keys notFoundMarker:[NSNull null]];
return [NSDictionary dictionaryWithObjects:keys forKeys:values];
}
- (NSString *)convertValue:(NSString *)value isImportingToModel:(BOOL)importing
{
return [self convertValue:value];
}
- (NSString *)convertValue:(NSString *)value
{
return _modelToJSONKeyBlock(value);
}
+ (instancetype)mapperFromUnderscoreCaseToCamelCase
{
return [self mapperForSnakeCase];
}
+ (instancetype)mapperForSnakeCase
{
return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
{
NSMutableString *result = [NSMutableString stringWithString:keyName];
NSRange range;
// handle upper case chars
range = [result rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]];
while (range.location != NSNotFound)
{
NSString *lower = [result substringWithRange:range].lowercaseString;
[result replaceCharactersInRange:range withString:[NSString stringWithFormat:@"_%@", lower]];
range = [result rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]];
}
// handle numbers
range = [result rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
while (range.location != NSNotFound)
{
NSRange end = [result rangeOfString:@"\\D" options:NSRegularExpressionSearch range:NSMakeRange(range.location, result.length - range.location)];
// spans to the end of the key name
if (end.location == NSNotFound)
end = NSMakeRange(result.length, 1);
NSRange replaceRange = NSMakeRange(range.location, end.location - range.location);
NSString *digits = [result substringWithRange:replaceRange];
[result replaceCharactersInRange:replaceRange withString:[NSString stringWithFormat:@"_%@", digits]];
range = [result rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet] options:0 range:NSMakeRange(end.location + 1, result.length - end.location - 1)];
}
return result;
}];
}
+ (instancetype)mapperForTitleCase
{
return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
{
return [keyName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[keyName substringToIndex:1].uppercaseString];
}];
}
+ (instancetype)mapperFromUpperCaseToLowerCase
{
return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
{
return keyName.uppercaseString;
}];
}
+ (instancetype)mapper:(JSONKeyMapper *)baseKeyMapper withExceptions:(NSDictionary *)exceptions
{
NSDictionary *toJSON = [JSONKeyMapper swapKeysAndValuesInDictionary:exceptions];
return [self baseMapper:baseKeyMapper withModelToJSONExceptions:toJSON];
}
+ (instancetype)baseMapper:(JSONKeyMapper *)baseKeyMapper withModelToJSONExceptions:(NSDictionary *)toJSON
{
return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
{
if (!keyName)
return nil;
if (toJSON[keyName])
return toJSON[keyName];
return baseKeyMapper.modelToJSONKeyBlock(keyName);
}];
}
@end
@@ -0,0 +1,209 @@
//
// JSONValueTransformer.h
// JSONModel
//
#import <Foundation/Foundation.h>
/////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - extern definitions
/**
* Boolean function to check for null values. Handy when you need to both check
* for nil and [NSNUll null]
*/
extern BOOL isNull(id value);
/////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - JSONValueTransformer interface
/**
* **You don't need to call methods of this class manually.**
*
* Class providing methods to transform values from one class to another.
* You are given a number of built-in transformers, but you are encouraged to
* extend this class with your own categories to add further value transformers.
* Just few examples of what can you add to JSONValueTransformer: hex colors in JSON to UIColor,
* hex numbers in JSON to NSNumber model properties, base64 encoded strings in JSON to UIImage properties, and more.
*
* The class is invoked by JSONModel while transforming incoming
* JSON types into your target class property classes, and vice versa.
* One static copy is create and store in the JSONModel class scope.
*/
@interface JSONValueTransformer : NSObject
@property (strong, nonatomic, readonly) NSDictionary *primitivesNames;
/** @name Resolving cluster class names */
/**
* This method returns the umbrella class for any standard class cluster members.
* For example returns NSString when given as input NSString, NSMutableString, __CFString and __CFConstantString
* The method currently looksup a pre-defined list.
* @param sourceClass the class to get the umbrella class for
* @return Class
*/
+ (Class)classByResolvingClusterClasses:(Class)sourceClass;
#pragma mark - NSMutableString <-> NSString
/** @name Transforming to Mutable copies */
/**
* Transforms a string value to a mutable string value
* @param string incoming string
* @return mutable string
*/
- (NSMutableString *)NSMutableStringFromNSString:(NSString *)string;
#pragma mark - NSMutableArray <-> NSArray
/**
* Transforms an array to a mutable array
* @param array incoming array
* @return mutable array
*/
- (NSMutableArray *)NSMutableArrayFromNSArray:(NSArray *)array;
#pragma mark - NSMutableDictionary <-> NSDictionary
/**
* Transforms a dictionary to a mutable dictionary
* @param dict incoming dictionary
* @return mutable dictionary
*/
- (NSMutableDictionary *)NSMutableDictionaryFromNSDictionary:(NSDictionary *)dict;
#pragma mark - NSSet <-> NSArray
/** @name Transforming Sets */
/**
* Transforms an array to a set
* @param array incoming array
* @return set with the array's elements
*/
- (NSSet *)NSSetFromNSArray:(NSArray *)array;
/**
* Transforms an array to a mutable set
* @param array incoming array
* @return mutable set with the array's elements
*/
- (NSMutableSet *)NSMutableSetFromNSArray:(NSArray *)array;
/**
* Transforms a set to an array
* @param set incoming set
* @return an array with the set's elements
*/
- (NSArray *)JSONObjectFromNSSet:(NSSet *)set;
/**
* Transforms a mutable set to an array
* @param set incoming mutable set
* @return an array with the set's elements
*/
- (NSArray *)JSONObjectFromNSMutableSet:(NSMutableSet *)set;
#pragma mark - BOOL <-> number/string
/** @name Transforming JSON types */
/**
* Transforms a number object to a bool number object
* @param number the number to convert
* @return the resulting number
*/
- (NSNumber *)BOOLFromNSNumber:(NSNumber *)number;
/**
* Transforms a number object to a bool number object
* @param string the string value to convert, "0" converts to NO, everything else to YES
* @return the resulting number
*/
- (NSNumber *)BOOLFromNSString:(NSString *)string;
/**
* Transforms a BOOL value to a bool number object
* @param number an NSNumber value coming from the model
* @return the result number
*/
- (NSNumber *)JSONObjectFromBOOL:(NSNumber *)number;
#pragma mark - string <-> number
/**
* Transforms a string object to a number object
* @param string the string to convert
* @return the resulting number
*/
- (NSNumber *)NSNumberFromNSString:(NSString *)string;
/**
* Transforms a number object to a string object
* @param number the number to convert
* @return the resulting string
*/
- (NSString *)NSStringFromNSNumber:(NSNumber *)number;
/**
* Transforms a string object to a nsdecimalnumber object
* @param string the string to convert
* @return the resulting number
*/
- (NSDecimalNumber *)NSDecimalNumberFromNSString:(NSString *)string;
/**
* Transforms a nsdecimalnumber object to a string object
* @param number the number to convert
* @return the resulting string
*/
- (NSString *)NSStringFromNSDecimalNumber:(NSDecimalNumber *)number;
#pragma mark - string <-> url
/** @name Transforming URLs */
/**
* Transforms a string object to an NSURL object
* @param string the string to convert
* @return the resulting url object
*/
- (NSURL *)NSURLFromNSString:(NSString *)string;
/**
* Transforms an NSURL object to a string
* @param url the url object to convert
* @return the resulting string
*/
- (NSString *)JSONObjectFromNSURL:(NSURL *)url;
#pragma mark - string <-> time zone
/** @name Transforming NSTimeZone */
/**
* Transforms a string object to an NSTimeZone object
* @param string the string to convert
* @return the resulting NSTimeZone object
*/
- (NSTimeZone *)NSTimeZoneFromNSString:(NSString *)string;
/**
* Transforms an NSTimeZone object to a string
* @param timeZone the time zone object to convert
* @return the resulting string
*/
- (NSString *)JSONObjectFromNSTimeZone:(NSTimeZone *)timeZone;
#pragma mark - string <-> date
/** @name Transforming Dates */
/**
* The following two methods are not public. This way if there is a category on converting
* dates it'll override them. If there isn't a category the default methods found in the .m
* file will be invoked. If these are public a warning is produced at the point of overriding
* them in a category, so they have to stay hidden here.
*/
//- (NSDate *)NSDateFromNSString:(NSString *)string;
//- (NSString *)JSONObjectFromNSDate:(NSDate *)date;
#pragma mark - number <-> date
/**
* Transforms a number to an NSDate object
* @param number the number to convert
* @return the resulting date
*/
- (NSDate *)NSDateFromNSNumber:(NSNumber *)number;
@end
@@ -0,0 +1,246 @@
//
// JSONValueTransformer.m
// JSONModel
//
#import "JSONValueTransformer.h"
#pragma mark - functions
extern BOOL isNull(id value)
{
if (!value) return YES;
if ([value isKindOfClass:[NSNull class]]) return YES;
return NO;
}
@implementation JSONValueTransformer
-(id)init
{
self = [super init];
if (self) {
_primitivesNames = @{@"f":@"float", @"i":@"int", @"d":@"double", @"l":@"long", @"B":@"BOOL", @"s":@"short",
@"I":@"unsigned int", @"L":@"usigned long", @"q":@"long long", @"Q":@"unsigned long long", @"S":@"unsigned short", @"c":@"char", @"C":@"unsigned char",
//and some famous aliases of primitive types
// BOOL is now "B" on iOS __LP64 builds
@"I":@"NSInteger", @"Q":@"NSUInteger", @"B":@"BOOL",
@"@?":@"Block"};
}
return self;
}
+(Class)classByResolvingClusterClasses:(Class)sourceClass
{
//check for all variations of strings
if ([sourceClass isSubclassOfClass:[NSString class]]) {
return [NSString class];
}
//check for all variations of numbers
if ([sourceClass isSubclassOfClass:[NSNumber class]]) {
return [NSNumber class];
}
//check for all variations of dictionaries
if ([sourceClass isSubclassOfClass:[NSArray class]]) {
return [NSArray class];
}
//check for all variations of arrays
if ([sourceClass isSubclassOfClass:[NSDictionary class]]) {
return [NSDictionary class];
}
//check for all variations of dates
if ([sourceClass isSubclassOfClass:[NSDate class]]) {
return [NSDate class];
}
//no cluster parent class found
return sourceClass;
}
#pragma mark - NSMutableString <-> NSString
-(NSMutableString*)NSMutableStringFromNSString:(NSString*)string
{
return [NSMutableString stringWithString:string];
}
#pragma mark - NSMutableArray <-> NSArray
-(NSMutableArray*)NSMutableArrayFromNSArray:(NSArray*)array
{
return [NSMutableArray arrayWithArray:array];
}
#pragma mark - NSMutableDictionary <-> NSDictionary
-(NSMutableDictionary*)NSMutableDictionaryFromNSDictionary:(NSDictionary*)dict
{
return [NSMutableDictionary dictionaryWithDictionary:dict];
}
#pragma mark - NSSet <-> NSArray
-(NSSet*)NSSetFromNSArray:(NSArray*)array
{
return [NSSet setWithArray:array];
}
-(NSMutableSet*)NSMutableSetFromNSArray:(NSArray*)array
{
return [NSMutableSet setWithArray:array];
}
-(id)JSONObjectFromNSSet:(NSSet*)set
{
return [set allObjects];
}
-(id)JSONObjectFromNSMutableSet:(NSMutableSet*)set
{
return [set allObjects];
}
//
// 0 converts to NO, everything else converts to YES
//
#pragma mark - BOOL <-> number/string
-(NSNumber*)BOOLFromNSNumber:(NSNumber*)number
{
if (isNull(number)) return [NSNumber numberWithBool:NO];
return [NSNumber numberWithBool: number.intValue==0?NO:YES];
}
-(NSNumber*)BOOLFromNSString:(NSString*)string
{
if (string != nil &&
([string caseInsensitiveCompare:@"true"] == NSOrderedSame ||
[string caseInsensitiveCompare:@"yes"] == NSOrderedSame)) {
return [NSNumber numberWithBool:YES];
}
return [NSNumber numberWithBool: ([string intValue]==0)?NO:YES];
}
-(NSNumber*)JSONObjectFromBOOL:(NSNumber*)number
{
return [NSNumber numberWithBool: number.intValue==0?NO:YES];
}
#pragma mark - string/number <-> float
-(float)floatFromObject:(id)obj
{
return [obj floatValue];
}
-(float)floatFromNSString:(NSString*)string
{
return [self floatFromObject:string];
}
-(float)floatFromNSNumber:(NSNumber*)number
{
return [self floatFromObject:number];
}
-(NSNumber*)NSNumberFromfloat:(float)f
{
return [NSNumber numberWithFloat:f];
}
#pragma mark - string <-> number
-(NSNumber*)NSNumberFromNSString:(NSString*)string
{
return [NSNumber numberWithDouble:[string doubleValue]];
}
-(NSString*)NSStringFromNSNumber:(NSNumber*)number
{
return [number stringValue];
}
-(NSDecimalNumber*)NSDecimalNumberFromNSString:(NSString*)string
{
return [NSDecimalNumber decimalNumberWithString:string];
}
-(NSString*)NSStringFromNSDecimalNumber:(NSDecimalNumber*)number
{
return [number stringValue];
}
#pragma mark - string <-> url
-(NSURL*)NSURLFromNSString:(NSString*)string
{
// do not change this behavior - there are other ways of overriding it
// see: https://github.com/jsonmodel/jsonmodel/pull/119
return [NSURL URLWithString:string];
}
-(NSString*)JSONObjectFromNSURL:(NSURL*)url
{
return [url absoluteString];
}
#pragma mark - string <-> date
-(NSDateFormatter*)importDateFormatter
{
static dispatch_once_t onceInput;
static NSDateFormatter* inputDateFormatter;
dispatch_once(&onceInput, ^{
inputDateFormatter = [[NSDateFormatter alloc] init];
[inputDateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[inputDateFormatter setDateFormat:@"yyyy-MM-dd'T'HHmmssZZZ"];
});
return inputDateFormatter;
}
-(NSDate*)__NSDateFromNSString:(NSString*)string
{
string = [string stringByReplacingOccurrencesOfString:@":" withString:@""]; // this is such an ugly code, is this the only way?
return [self.importDateFormatter dateFromString: string];
}
-(NSString*)__JSONObjectFromNSDate:(NSDate*)date
{
static dispatch_once_t onceOutput;
static NSDateFormatter *outputDateFormatter;
dispatch_once(&onceOutput, ^{
outputDateFormatter = [[NSDateFormatter alloc] init];
[outputDateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[outputDateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZ"];
});
return [outputDateFormatter stringFromDate:date];
}
#pragma mark - number <-> date
- (NSDate*)NSDateFromNSNumber:(NSNumber*)number
{
return [NSDate dateWithTimeIntervalSince1970:number.doubleValue];
}
#pragma mark - string <-> NSTimeZone
- (NSTimeZone *)NSTimeZoneFromNSString:(NSString *)string {
return [NSTimeZone timeZoneWithName:string];
}
- (id)JSONObjectFromNSTimeZone:(NSTimeZone *)timeZone {
return [timeZone name];
}
#pragma mark - hidden transform for empty dictionaries
//https://github.com/jsonmodel/jsonmodel/issues/163
-(NSDictionary*)__NSDictionaryFromNSArray:(NSArray*)array
{
if (array.count==0) return @{};
return (id)array;
}
-(NSMutableDictionary*)__NSMutableDictionaryFromNSArray:(NSArray*)array
{
if (array.count==0) return [[self __NSDictionaryFromNSArray:array] mutableCopy];
return (id)array;
}
@end
+18
View File
@@ -0,0 +1,18 @@
Copyright (c) 2012-2016 Marin Todorov and JSONModel contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+395
View File
@@ -0,0 +1,395 @@
# JSONModel - Magical Data Modeling Framework for JSON
JSONModel allows rapid creation of smart data models. You can use it in your
iOS, macOS, watchOS and tvOS apps. Automatic introspection of your model classes
and JSON input drastically reduces the amount of code you have to write.
See [CHANGELOG.md](CHANGELOG.md) for details on changes.
## Installation
### CocoaPods
```ruby
pod 'JSONModel'
```
### Carthage
```ruby
github "jsonmodel/jsonmodel"
```
### Manual
0. download the JSONModel repository
0. copy the JSONModel sub-folder into your Xcode project
0. link your app to SystemConfiguration.framework
## Basic Usage
Consider you have JSON like this:
```json
{ "id": 10, "country": "Germany", "dialCode": 49, "isInEurope": true }
```
- create a JSONModel subclass for your data model
- declare properties in your header file with the name of the JSON keys:
```objc
@interface CountryModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *country;
@property (nonatomic) NSString *dialCode;
@property (nonatomic) BOOL isInEurope;
@end
```
There's no need to do anything in the implementation (`.m`) file.
- initialize your model with data:
```objc
NSError *error;
CountryModel *country = [[CountryModel alloc] initWithString:myJson error:&error];
```
If the validation of the JSON passes. you have all the corresponding properties
in your model populated from the JSON. JSONModel will also try to convert as
much data to the types you expect. In the example above it will:
- convert `id` from string (in the JSON) to an `int` for your class
- copy the `country` value
- convert `dialCode` from a number (in the JSON) to an `NSString` value
- copy the `isInEurope` value
All you have to do is define the properties and their expected types.
## Examples
### Automatic name based mapping
```json
{
"id": 123,
"name": "Product name",
"price": 12.95
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@end
```
### Model cascading (models including other models)
```json
{
"orderId": 104,
"totalPrice": 13.45,
"product": {
"id": 123,
"name": "Product name",
"price": 12.95
}
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@end
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) float totalPrice;
@property (nonatomic) ProductModel *product;
@end
```
### Model collections
```json
{
"orderId": 104,
"totalPrice": 103.45,
"products": [
{
"id": 123,
"name": "Product #1",
"price": 12.95
},
{
"id": 137,
"name": "Product #2",
"price": 82.95
}
]
}
```
```objc
@protocol ProductModel;
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@end
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) float totalPrice;
@property (nonatomic) NSArray <ProductModel> *products;
@end
```
Note: the angle brackets after `NSArray` contain a protocol. This is not the
same as the Objective-C generics system. They are not mutually exclusive, but
for JSONModel to work, the protocol must be in place.
Also property can have generics info for compiler
```objc
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) float totalPrice;
@property (nonatomic) NSArray<ProductModel *> <ProductModel> *products;
@end
```
### Nested key mapping
```json
{
"orderId": 104,
"orderDetails": {
"name": "Product #1",
"price": {
"usd": 12.95
}
}
}
```
```objc
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *productName;
@property (nonatomic) float price;
@end
@implementation OrderModel
+ (JSONKeyMapper *)keyMapper
{
return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
@"id": @"orderId",
@"productName": @"orderDetails.name",
@"price": @"orderDetails.price.usd"
}];
}
@end
```
### Map automatically to snake_case
```json
{
"order_id": 104,
"order_product": "Product #1",
"order_price": 12.95
}
```
```objc
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) NSString *orderProduct;
@property (nonatomic) float orderPrice;
@end
@implementation OrderModel
+ (JSONKeyMapper *)keyMapper
{
return [JSONKeyMapper mapperForSnakeCase];
}
@end
```
### Optional properties (i.e. can be missing or null)
```json
{
"id": 123,
"name": null,
"price": 12.95
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString <Optional> *name;
@property (nonatomic) float price;
@property (nonatomic) NSNumber <Optional> *uuid;
@end
```
### Ignored properties (i.e. JSONModel completely ignores them)
```json
{
"id": 123,
"name": null
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString <Ignore> *customProperty;
@end
```
### Making scalar types optional
```json
{
"id": null
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@end
@implementation ProductModel
+ (BOOL)propertyIsOptional:(NSString *)propertyName
{
if ([propertyName isEqualToString:@"id"])
return YES;
return NO;
}
@end
```
### Export model to `NSDictionary` or JSON
```objc
ProductModel *pm = [ProductModel new];
pm.name = @"Some Name";
// convert to dictionary
NSDictionary *dict = [pm toDictionary];
// convert to json
NSString *string = [pm toJSONString];
```
### Custom data transformers
```objc
@interface JSONValueTransformer (CustomTransformer)
@end
@implementation JSONValueTransformer (CustomTransformer)
- (NSDate *)NSDateFromNSString:(NSString *)string
{
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = APIDateFormat;
return [formatter dateFromString:string];
}
- (NSString *)JSONObjectFromNSDate:(NSDate *)date
{
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = APIDateFormat;
return [formatter stringFromDate:date];
}
@end
```
### Custom getters/setters
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@property (nonatomic) NSLocale *locale;
@end
@implementation ProductModel
- (void)setLocaleWithNSString:(NSString *)string
{
self.locale = [NSLocale localeWithLocaleIdentifier:string];
}
- (void)setLocaleWithNSDictionary:(NSDictionary *)dictionary
{
self.locale = [NSLocale localeWithLocaleIdentifier:dictionary[@"identifier"]];
}
- (NSString *)JSONObjectForLocale
{
return self.locale.localeIdentifier;
}
@end
```
### Custom JSON validation
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@property (nonatomic) NSLocale *locale;
@property (nonatomic) NSNumber <Ignore> *minNameLength;
@end
@implementation ProductModel
- (BOOL)validate:(NSError **)error
{
if (![super validate:error])
return NO;
if (self.name.length < self.minNameLength.integerValue)
{
*error = [NSError errorWithDomain:@"me.mycompany.com" code:1 userInfo:nil];
return NO;
}
return YES;
}
@end
```
## License
MIT licensed - see [LICENSE](LICENSE) file.
## Contributing
We love pull requests! See [CONTRIBUTING.md](CONTRIBUTING.md) for full details.