JSON to Model: Mantle

简介:mantle属于JSON转model的第三方库,利用mantle可以方便的利用JSON文件生成相应的model。作为一种被广泛使用的库,其具有以下优点:

1、字段映射比较方便。

2、扩展时比较方便。

3、可以实现很多复杂的映射关系和数值转换。

4、实现了NSCopying和NSCoding协议,可以轻松序列化。

强大的功能使得代码比较称重,在使用时的效率相比其它如YYModel,JSONModel等库有所欠缺。

首先JSON支持4中基本类型,包括字符串、数字、布尔型(true,false)、NULL;

同时支持两种结构:1、对象,即字典({"name":"zhangsan","age":23}),

  •                2、数组,[1,2,3]。

这些类型和结构都和oc中的类型结构一一对应,可以进行相应的转换,但是值得注意的是mantle不支持不同类型的转换,如int转NSString。

  1. Mantle的使用方法
  • 使用Mantle时需使我们自定义的model继承MTLModel类,并遵循MTLJSONSerializing协议,其中在MTLJSONSerializing协议中有一个@required和两个@optional方法

@required

+ (NSDictionary *)JSONKeyPathsByPropertyKey;

@optional:

+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;

+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary;

在JSONKeyPathsByPropertyKey方法中定义了JSON中的key与oc model中的key的这种一一对应的关系,返回值为一个字典,key为model中的property name,值为JSON中的key值。示例如下所示:

+ (NSDictionary *)JSONKeyPathsByPropertyKey

{

    return @{

             @"id"         : @"id",

             @"country"    : @"country",

             @"dialCode"   : @"dialCode",

             @"isInEurope" : @"isInEurope",

             @"status"     : @"status"

             };

}

+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key提供了一种关于属性名为key的转换方式:

+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key

{

    if ([key isEqualToString:@"id"]) {

        return [MTLValueTransformer transformerUsingForwardBlock:

                ^id(NSNumber *value, BOOL *success, NSError **error)

                {

                    NSString *temStr = [NSString stringWithFormat:@"%@",value];

                    return temStr;

                }];

    }

    return nil;

}

方法内部会定义一个block来表示这种转换关系,这里只定义了前向转换,及从JSON转model,在这个block中,value来自JSON文件,属于JSON文件中的几种类型之一。

+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary方法主要解决要利用聚类的方式解析model的问题(用的比较少)。

另外在model中可能还含有字model,array,枚举等属性,这种情况下是需要我们自定义这种转换关系的,对于不用类型的属性,转换方式也不太一样,具体对应的转换方式如下:

类对象

+ (NSValueTransformer *)avatarDecorationModelJSONTransformer

{

    return [MTLJSONAdapter dictionaryTransformerWithModelClass:[AWEUserAvatarDecotationModel class]];

}

数组对象

+ (NSValueTransformer *)followerDetailModelsJSONTransformer

{

    return [MTLJSONAdapter arrayTransformerWithModelClass:[AWEUserFollowersDetailModel class]];

}

枚举对象

+ (NSValueTransformer *)genderJSONTransformer

{

    NSDictionary *transformDictionary = @{[NSNull null] : @(AWEUserGenderTypeUnDefined),

                                          @(0)          : @(AWEUserGenderTypeMale),

                                          @(1)          : @(AWEUserGenderTypeFemale),

                                          @(2)          : @(AWEUserGenderTypeUnknown)};

    return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:transformDictionary

                                                            defaultValue:@(AWEUserGenderTypeUnDefined)

                                                     reverseDefaultValue:@"undefined"];

}

简单对象,如int转nsstring

+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key

{

    if ([key isEqualToString:@"id"]) {

        return [MTLValueTransformer transformerUsingForwardBlock:

                ^id(NSNumber *value, BOOL *success, NSError **error)

                {

                    NSString *temStr = [NSString stringWithFormat:@"%@",value];

                    return temStr;

                }];

    }

    return nil;

}

值得注意的是,子model也必须继承mantle同时遵循MTLJSONSerializing协议。

dictionaryTransformerWithModelClass的具体实现如下所示:

+ (NSValueTransformer<MTLTransformerErrorHandling> *)dictionaryTransformerWithModelClass:(Class)modelClass {

        NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLModel)]);

        NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);

        __block MTLJSONAdapter *adapter;

        

        return [MTLValueTransformer

                transformerUsingForwardBlock:^ id (id JSONDictionary, BOOL *success, NSError **error) {

                        if (JSONDictionary == nilreturn nil;

                        

                        if (![JSONDictionary isKindOfClass:NSDictionary.class]) {

                                if (error != NULL) {

                                        NSDictionary *userInfo = @{

                                                NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert JSON dictionary to model object", @""),

                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected an NSDictionary, got: %@", @""), JSONDictionary],

                                                MTLTransformerErrorHandlingInputValueErrorKey : JSONDictionary

                                        };

                                        

                                        *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];

                                }

                                *success = NO;

                                return nil;

                        }

 

                        if (!adapter) {

                                adapter = [[self alloc] initWithModelClass:modelClass];

                        }

                        id model = [adapter modelFromJSONDictionary:JSONDictionary error:error];

                        if (model == nil) {

                                *success = NO;

                        }

 

                        return model;

                }

                reverseBlock:^ NSDictionary * (id model, BOOL *success, NSError **error) {

                        if (model == nilreturn nil;

                        

                        if (![model conformsToProtocol:@protocol(MTLModel)] || ![model conformsToProtocol:@protocol(MTLJSONSerializing)]) {

                                if (error != NULL) {

                                        NSDictionary *userInfo = @{

                                                NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert model object to JSON dictionary", @""),

                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected a MTLModel object conforming to <MTLJSONSerializing>, got: %@.", @""), model],

                                                MTLTransformerErrorHandlingInputValueErrorKey : model

                                        };

                                        

                                        *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];

                                }

                                *success = NO;

                                return nil;

                        }

 

                        if (!adapter) {

                                adapter = [[self alloc] initWithModelClass:modelClass];

                        }

                        NSDictionary *result = [adapter JSONDictionaryFromModel:model error:error];

                        if (result == nil) {

                                *success = NO;

                        }

 

                        return result;

                }];

}

上述方法中会定义两个block,分别指定了前向转换和后向转换的方法,其中前向转换中会调用

  adapter = [[self alloc] initWithModelClass:modelClass];

  id model = [adapter modelFromJSONDictionary:JSONDictionary error:error];

这就形成了一个循环,可以将子model进行转换。

当所有关于model的转换方式都定义好了以后,生成model是只需要调用下面一句话:

CountryModelBasedMTLModel *country2 = [MTLJSONAdapter modelOfClass:[CountryModelBasedMTLModel class] fromJSONDictionary:jsonDict error:&MTLError];

参数分别我们需要转换的对象类型以及JSON字典文件,其中个JSON字典的生成方式如下:

NSBundle *bundle = [NSBundle mainBundle];

NSString *jsonPath = [bundle pathForResource:@"Resource/JSON/country" ofType:@"json"];

NSData *data = [[NSData alloc] initWithContentsOfFile:jsonPath];

NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];

这个方法调用比较简单,内部调用了两个方法:

  1. MTLJSONAdapter *adapter = [[self alloc] initWithModelClass:modelClass];
  1. return [adapter modelFromJSONDictionary:JSONDictionary error:error];

在第一个方法中会生成一个MTLJSONAdapter的实例,里面包含了propertyKey和JSONkey的这种对应关系、JSON变量到property实例变量的这种值得转换关系等。具体代码如下所示:

- (id)initWithModelClass:(Class)modelClass {

        NSParameterAssert(modelClass != nil);

        NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);

 

        self = [super init];

        if (self == nilreturn nil;

 

        _modelClass = modelClass;

 

        _JSONKeyPathsByPropertyKey = [modelClass JSONKeyPathsByPropertyKey];

 

        NSSet *propertyKeys = [self.modelClass propertyKeys];

 

        for (NSString *mappedPropertyKey in _JSONKeyPathsByPropertyKey) {

                if (![propertyKeys containsObject:mappedPropertyKey]) {

                        NSAssert(NO, @"%@ is not a property of %@.", mappedPropertyKey, modelClass);

                        return nil;

                }

 

                id value = _JSONKeyPathsByPropertyKey[mappedPropertyKey];

 

                if ([value isKindOfClass:NSArray.class]) {

                        for (NSString *keyPath in value) {

                                if ([keyPath isKindOfClass:NSString.class]) continue;

 

                                NSAssert(NO, @"%@ must either map to a JSON key path or a JSON array of key paths, got: %@.", mappedPropertyKey, value);

                                return nil;

                        }

                } else if (![value isKindOfClass:NSString.class]) {

                        NSAssert(NO, @"%@ must either map to a JSON key path or a JSON array of key paths, got: %@.",mappedPropertyKey, value);

                        return nil;

                }

        }

 

        _valueTransformersByPropertyKey = [self.class valueTransformersForModelClass:modelClass];

 

        _JSONAdaptersByModelClass = [NSMapTable strongToStrongObjectsMapTable];

 

        return self;

}

调用完上面方法后,所有的key对应关系以及值之间的转换关系都被存储在MTLJSONAdapter实例的属性中,最后在调用

 [adapter modelFromJSONDictionary:JSONDictionary error:error];

此方法生成我们最终的oc实例对象,该方法的具体实现如下所示:

- (id)modelFromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error {

        if ([self.modelClass respondsToSelector:@selector(classForParsingJSONDictionary:)]) {

                Class class = [self.modelClass classForParsingJSONDictionary:JSONDictionary];

                if (class == nil) {

                        if (error != NULL) {

                                NSDictionary *userInfo = @{

                                        NSLocalizedDescriptionKey: NSLocalizedString(@"Could not parse JSON", @""),

                                        NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"No model class could be found to parse the JSON dictionary.", @"")

                                };

 

                                *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorNoClassFound userInfo:userInfo];

                        }

 

                        return nil;

                }

 

                if (class != self.modelClass) {

                        NSAssert([class conformsToProtocol:@protocol(MTLJSONSerializing)], @"Class %@ returned from +classForParsingJSONDictionary: does not conform to <MTLJSONSerializing>", class);

 

                        MTLJSONAdapter *otherAdapter = [self JSONAdapterForModelClass:class error:error];

 

                        return [otherAdapter modelFromJSONDictionary:JSONDictionary error:error];

                }

        }

 

        NSMutableDictionary *dictionaryValue = [[NSMutableDictionary alloc] initWithCapacity:JSONDictionary.count];

 

        for (NSString *propertyKey in [self.modelClass propertyKeys]) {

                id JSONKeyPaths = self.JSONKeyPathsByPropertyKey[propertyKey];

 

                if (JSONKeyPaths == nilcontinue;

 

                id value;

 

                if ([JSONKeyPaths isKindOfClass:NSArray.class]) {

                        NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];

 

                        for (NSString *keyPath in JSONKeyPaths) {

                                BOOL success = NO;

                                id value = [JSONDictionary mtl_valueForJSONKeyPath:keyPath success:&success error:error];

 

                                if (!success) return nil;

 

                                if (value != nil) dictionary[keyPath] = value;

                        }

 

                        value = dictionary;

                } else {

                        BOOL success = NO;

                        value = [JSONDictionary mtl_valueForJSONKeyPath:JSONKeyPaths success:&success error:error];

 

                        if (!success) return nil;

                }

 

                if (value == nilcontinue;

 

                @try {

                        NSValueTransformer *transformer = self.valueTransformersByPropertyKey[propertyKey];

                        if (transformer != nil) {

                                // Map NSNull -> nil for the transformer, and then back for the

                                // dictionary we're going to insert into.

                                if ([value isEqual:NSNull.null]) value = nil;

 

                                if ([transformer respondsToSelector:@selector(transformedValue:success:error:)]) {

                                        id<MTLTransformerErrorHandling> errorHandlingTransformer = (id)transformer;

 

                                        BOOL success = YES;

                                        value = [errorHandlingTransformer transformedValue:value success:&success error:error];

 

                                        if (!success) return nil;

                                } else {

                                        value = [transformer transformedValue:value];

                                }

 

                                if (value == nil) value = NSNull.null;

                        }

 

                        dictionaryValue[propertyKey] = value;

                } @catch (NSException *ex) {

                        NSLog(@"*** Caught exception %@ parsing JSON key path \"%@\" from: %@", ex, JSONKeyPaths, JSONDictionary);

 

                        // Fail fast in Debug builds.

                        #if DEBUG

                        @throw ex;

                        #else

                        if (error != NULL) {

                                NSDictionary *userInfo = @{

                                        NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Caught exception parsing JSON key path \"%@\" for model class: %@", JSONKeyPaths, self.modelClass],

                                        NSLocalizedRecoverySuggestionErrorKey: ex.description,

                                        NSLocalizedFailureReasonErrorKey: ex.reason,

                                        MTLJSONAdapterThrownExceptionErrorKey: ex

                                };

 

                                *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorExceptionThrown userInfo:userInfo];

                        }

 

                        return nil;

                        #endif

                }

        }

 

        id model = [self.modelClass modelWithDictionary:dictionaryValue error:error];

 

        return [model validate:error] ? model : nil;

}

该方法主要范围三步,

1、根据property key获取JSON文件中的key,然后根据该key值获得JSON文件中的value。

2、根据property key获取transformer,所有的转换关系都存储在MTLJSONAdapter实例的一个属性中,该属性是一个字典,其中key为property key,值为transformer。

3、根据该transformer将JSON中的value转换为oc中的value,最后将结果存储在一个oc字典中。

执行完上述三步中你以为就完了吗,其实并没有,目前为止,我们只得到了一个字典,并没有生成我们需要的model实例,接下来会执行:

[self.modelClass modelWithDictionary:dictionaryValue error:error];

该方法内部的基本原理是利用KVC来对属性变量进行赋值,具体实现如下:

[obj setValue:validatedValue forKey:key];

 

值得注意的是在主体方法

[MTLJSONAdapter modelOfClass:[CountryModelBasedMTLModel class] fromJSONDictionary:jsonDict error:&MTLError];

内部会大量使用runtime来动态获取model的属性,如会采用

        while (!stop && ![cls isEqual:MTLModel.class]) {

                unsigned count = 0;

                objc_property_t *properties = class_copyPropertyList(cls, &count);

 

                cls = cls.superclass;

                if (properties == NULLcontinue;

 

                @onExit {

                        free(properties);

                };

 

                for (unsigned i = 0; i < count; i++) {

                        block(properties[i], &stop);

                        if (stop) break;

                }

        }

来获取model中的属性等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值