Initial commit

This commit is contained in:
lianxiang
2018-08-22 11:15:52 +08:00
parent d98e20881f
commit 249bf6864e
491 changed files with 68665 additions and 7 deletions
+274
View File
@@ -0,0 +1,274 @@
//
// 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&lt;Ignore&gt; *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&lt;Optional&gt; *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 *)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 *)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 *)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
*/
+ (Class)classForCollectionProperty:(NSString *)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 *)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,178 @@
//
// JSONKeyMapper.m
// JSONModel
//
#import "JSONKeyMapper.h"
#import <libkern/OSAtomic.h>
@interface JSONKeyMapper()
@property (nonatomic, strong) NSMutableDictionary *toJSONMap;
@property (nonatomic, assign) OSSpinLock lock;
@end
@implementation JSONKeyMapper
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_toJSONMap = [NSMutableDictionary new];
return self;
}
- (instancetype)initWithJSONToModelBlock:(JSONModelKeyMapBlock)toModel modelToJSONBlock:(JSONModelKeyMapBlock)toJSON
{
return [self initWithModelToJSONBlock:toJSON];
}
- (instancetype)initWithModelToJSONBlock:(JSONModelKeyMapBlock)toJSON
{
if (!(self = [self init]))
return nil;
__weak JSONKeyMapper *weakSelf = self;
_modelToJSONKeyBlock = ^NSString *(NSString *keyName)
{
__strong JSONKeyMapper *strongSelf = weakSelf;
id cached = strongSelf.toJSONMap[keyName];
if (cached == [NSNull null])
return nil;
if (cached)
return strongSelf.toJSONMap[keyName];
NSString *result = toJSON(keyName);
OSSpinLockLock(&strongSelf->_lock);
strongSelf.toJSONMap[keyName] = result ? result : [NSNull null];
OSSpinLockUnlock(&strongSelf->_lock);
return result;
};
return self;
}
- (instancetype)initWithDictionary:(NSDictionary *)map
{
NSDictionary *toJSON = [JSONKeyMapper swapKeysAndValuesInDictionary:map];
return [self initWithModelToJSONDictionary:toJSON];
}
- (instancetype)initWithModelToJSONDictionary:(NSDictionary *)toJSON
{
if (!(self = [super init]))
return nil;
_modelToJSONKeyBlock = ^NSString *(NSString *keyName)
{
return [toJSON valueForKeyPath:keyName] ?: keyName;
};
return self;
}
+ (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,245 @@
//
// 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", @"c":@"BOOL", @"s":@"short", @"q":@"long",
//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