Initial commit
This commit is contained in:
+284
@@ -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
|
||||
+1387
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
@@ -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
@@ -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
|
||||
Generated
+19
@@ -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"
|
||||
@@ -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
@@ -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 {
|
||||
* return [[JSONKeyMapper alloc] initWithDictionary:@{@"crazy_JSON_name":@"myCamelCaseName"}];
|
||||
* }
|
||||
* </pre>
|
||||
* In case you want to handle underscore_case, **use the predefined key mapper**, like so:
|
||||
* <pre>
|
||||
* + (JSONKeyMapper *)keyMapper {
|
||||
* return [JSONKeyMapper 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
|
||||
Generated
+18
@@ -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.
|
||||
Generated
+395
@@ -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.
|
||||
Reference in New Issue
Block a user