diff --git a/README.md b/README.md index ea38047..e03c914 100755 --- a/README.md +++ b/README.md @@ -53,10 +53,10 @@ Usage @implementation User @end - + // Convert json to model: User *user = [User yy_modelWithJSON:json]; - + // Convert model to json: NSDictionary *json = [user yy_modelToJSONObject]; @@ -147,6 +147,33 @@ EEE MMM dd HH:mm:ss Z yyyy } @end + // OR + + // JSON: + { + "Name":"Harry Pottery", + "Page": 256, + "Description": "A book written by J.K.Rowling." + "BookID" : 100010 + } + + // Model: + @interface Book : NSObject + @property NSString *name; + @property NSInteger page; + @property NSString *desc; + @property NSString *bookID; + @end + @implementation Book + + (id)modelCustomKeyNameWithPropertyName:(NSString *)propertyName { + if ([propertyName isEqualToString:@"desc"]) { + return @"Description"; + } + // first letter uppercase + return [NSString stringWithFormat:@"%@%@", [[propertyName substringToIndex:1] uppercaseString], [propertyName substringFromIndex:1]]; + } + @end + You can map a json key (key path) or an array of json key (key path) to one or multiple property name. If there's no mapper for a property, it will use the property's name as default. ###Nested model @@ -168,7 +195,7 @@ You can map a json key (key path) or an array of json key (key path) to one or m @end @implementation Author @end - + @interface Book : NSObject @property NSString *name; @property NSUInteger pages; @@ -176,8 +203,8 @@ You can map a json key (key path) or an array of json key (key path) to one or m @end @implementation Book @end - - + + ### Container property @@ -208,7 +235,7 @@ You can map a json key (key path) or an array of json key (key path) to one or m @property NSString *name; @property NSUInteger age; @end - + @implementation Attributes + (NSArray *)modelPropertyBlacklist { return @[@"test1", @"test2"]; @@ -219,13 +246,13 @@ You can map a json key (key path) or an array of json key (key path) to one or m @end ###Data validate and custom transform - + // JSON: { "name":"Harry", "timestamp" : 1445534567 } - + // Model: @interface User @property NSString *name; @@ -351,10 +378,10 @@ YYModel is provided under the MIT license. See LICENSE file for details. @implementation User @end - + // 将 JSON (NSData,NSString,NSDictionary) 转换为 Model: User *user = [User yy_modelWithJSON:json]; - + // 将 Model 转换为 JSON 对象: NSDictionary *json = [user yy_modelToJSONObject]; @@ -443,13 +470,43 @@ EEE MMM dd HH:mm:ss Z yyyy @"bookID" : @[@"id",@"ID",@"book_id"]}; } @end + + // 或者 + // JSON: + { + "Name":"Harry Pottery", + "Page": 256, + "Description": "A book written by J.K.Rowling." + "BookID" : 100010 + } + + // Model: + @interface Book : NSObject + @property NSString *name; + @property NSInteger page; + @property NSString *desc; + @property NSString *bookID; + @end + @implementation Book + //返回propertyName 对映射到 JSON 的 Key。 + + (id)modelCustomKeyNameWithPropertyName:(NSString *)propertyName { + if ([propertyName isEqualToString:@"desc"]) { + return @"Description"; + } + // 首字母大字 + return [NSString stringWithFormat:@"%@%@", [[propertyName substringToIndex:1] uppercaseString], [propertyName substringFromIndex:1]]; + } + @end + 你可以把一个或一组 json key (key path) 映射到一个或多个属性。如果一个属性没有映射关系,那默认会使用相同属性名作为映射。 在 json->model 的过程中:如果一个属性对应了多个 json key,那么转换过程会按顺序查找,并使用第一个不为空的值。 - + 在 model->json 的过程中:如果一个属性对应了多个 json key (key path),那么转换过程仅会处理第一个 json key (key path);如果多个属性对应了同一个 json key,则转换过过程会使用其中任意一个不为空的值。 +modelCustomPropertyMapper和modelCustomKeyNameWithPropertyName只能使用其中一个,如果同时使用只有modelCustomPropertyMapper有效。 + ###Model 包含其他 Model // JSON @@ -469,7 +526,7 @@ EEE MMM dd HH:mm:ss Z yyyy @end @implementation Author @end - + @interface Book : NSObject @property NSString *name; @property NSUInteger pages; @@ -477,8 +534,8 @@ EEE MMM dd HH:mm:ss Z yyyy @end @implementation Book @end - - + + ###容器类属性 @@ -509,7 +566,7 @@ EEE MMM dd HH:mm:ss Z yyyy @property NSString *name; @property NSUInteger age; @end - + @implementation Attributes // 如果实现了该方法,则处理过程中会忽略该列表内的所有属性 + (NSArray *)modelPropertyBlacklist { @@ -522,13 +579,13 @@ EEE MMM dd HH:mm:ss Z yyyy @end ###数据校验与自定义转换 - + // JSON: { "name":"Harry", "timestamp" : 1445534567 } - + // Model: @interface User @property NSString *name; @@ -545,7 +602,7 @@ EEE MMM dd HH:mm:ss Z yyyy _createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue]; return YES; } - + // 当 Model 转为 JSON 完成后,该方法会被调用。 // 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。 // 你也可以在这里做一些自动转换不能完成的工作。 diff --git a/YYModel/NSObject+YYModel.h b/YYModel/NSObject+YYModel.h index 82032ec..b8a0a50 100644 --- a/YYModel/NSObject+YYModel.h +++ b/YYModel/NSObject+YYModel.h @@ -269,6 +269,8 @@ NS_ASSUME_NONNULL_BEGIN @discussion If the key in JSON/Dictionary does not match to the model's property name, implements this method and returns the additional mapper. + modelCustomPropertyMapper and modelCustomKeyNameWithPropertyName: Use only one of them. + Example: json: @@ -302,6 +304,50 @@ NS_ASSUME_NONNULL_BEGIN */ + (nullable NSDictionary *)modelCustomPropertyMapper; +/** + + Custom property name. + + @discussion If the key in JSON/Dictionary does not match to the model's property name + and has certain rules, implements this method and returns the corresponding + JSON/Dictionary key name. + + modelCustomPropertyMapper and modelCustomKeyNameWithPropertyName: Use only one of them. + + Example: + + json: + { + "Name":"Harry Pottery", + "Page": 256, + "Description": "A book written by J.K.Rowling." + "BookID" : 100010 + } + + model: + @interface YYBook : NSObject + @property NSString *name; + @property NSInteger page; + @property NSString *desc; + @property NSString *bookID; + @end + + @implementation YYBook + + (id)modelCustomKeyNameWithPropertyName:(NSString *)propertyName { + if ([propertyName isEqualToString:@"desc"]) { + return @"Description"; + } + // first letter uppercase + return [NSString stringWithFormat:@"%@%@", [[propertyName substringToIndex:1] uppercaseString], [propertyName substringFromIndex:1]]; + } + @end + + @param propertyName The model property name. + + @return A JSON/Dictionary key name. + */ ++ (nullable id)modelCustomKeyNameWithPropertyName:(NSString *)propertyName; + /** The generic class mapper for container properties. diff --git a/YYModel/NSObject+YYModel.m b/YYModel/NSObject+YYModel.m index e92a6b1..14a8285 100644 --- a/YYModel/NSObject+YYModel.m +++ b/YYModel/NSObject+YYModel.m @@ -546,8 +546,18 @@ - (instancetype)initWithClass:(Class)cls { NSMutableArray *keyPathPropertyMetas = [NSMutableArray new]; NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new]; + NSDictionary *customMapper = nil; if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) { - NSDictionary *customMapper = [(id )cls modelCustomPropertyMapper]; + customMapper = [(id )cls modelCustomPropertyMapper]; + } else if ([cls respondsToSelector:@selector(modelCustomKeyNameWithPropertyName:)]) { + NSMutableDictionary *tempCustomMapper = [NSMutableDictionary new]; + [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, id _Nonnull obj, BOOL * _Nonnull stop) { + id mappedToKey = [(id )cls modelCustomKeyNameWithPropertyName:propertyName]; + if (mappedToKey) tempCustomMapper[propertyName] = mappedToKey; + }]; + if (tempCustomMapper.count) customMapper = tempCustomMapper; + } + if (customMapper) { [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) { _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName]; if (!propertyMeta) return; diff --git a/YYModelTests/YYTestModelMapper.m b/YYModelTests/YYTestModelMapper.m index 4b52c8d..1a03cc6 100644 --- a/YYModelTests/YYTestModelMapper.m +++ b/YYModelTests/YYTestModelMapper.m @@ -32,6 +32,12 @@ @interface YYTestPropertyMapperModelCustom : NSObject @end @implementation YYTestPropertyMapperModelCustom +@end + +@interface YYTestPropertyMapperModelCustom1 : YYTestPropertyMapperModelCustom +@end + +@implementation YYTestPropertyMapperModelCustom1 + (NSDictionary *)modelCustomPropertyMapper { return @{ @"name" : @"n", @"count" : @"ext.c", @@ -43,6 +49,16 @@ + (NSDictionary *)modelCustomPropertyMapper { } @end +@interface YYTestPropertyMapperModelCustom2 : YYTestPropertyMapperModelCustom +@end + +@implementation YYTestPropertyMapperModelCustom2 ++ (id)modelCustomKeyNameWithPropertyName:(NSString *)propertyName { + NSDictionary *mapper = [YYTestPropertyMapperModelCustom1 modelCustomPropertyMapper]; + return mapper[propertyName]; +} +@end + @interface YYTestPropertyMapperModelWarn : NSObject { NSString *_description; } @@ -131,30 +147,30 @@ - (void)testAuto { XCTAssertTrue([model.count isEqual:@12]); } -- (void)testCustom { +- (void)_testCustomWithModel:(nonnull YYTestPropertyMapperModelCustom *)model { NSString *json; NSDictionary *jsonObject; - YYTestPropertyMapperModelCustom *model; + Class modelClass = [model class]; json = @"{\"n\":\"Apple\",\"ext\":{\"c\":12}}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue([model.name isEqualToString:@"Apple"]); XCTAssertTrue([model.count isEqual:@12]); json = @"{\"n\":\"Apple\",\"count\":12}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue(model.count == nil); json = @"{\"n\":\"Apple\",\"ext\":12}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue(model.count == nil); json = @"{\"n\":\"Apple\",\"ext\":@{}}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue(model.count == nil); json = @"{\"ext\":{\"d\":\"Apple\"}}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue([model.desc1 isEqualToString:@"Apple"]); XCTAssertTrue([model.desc2 isEqualToString:@"Apple"]); @@ -162,27 +178,27 @@ - (void)testCustom { XCTAssertTrue([((NSDictionary *)jsonObject[@"ext"])[@"d"] isEqualToString:@"Apple"]); json = @"{\"ext\":{\"d\":{ \"e\" : \"Apple\"}}}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue([model.desc3 isEqualToString:@"Apple"]); json = @"{\".ext\":\"Apple\"}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue([model.desc4 isEqualToString:@"Apple"]); json = @"{\".ext\":\"Apple\", \"name\":\"Apple\", \"count\":\"10\", \"desc1\":\"Apple\", \"desc2\":\"Apple\", \"desc3\":\"Apple\", \"desc4\":\"Apple\", \"modelID\":\"Apple\"}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue([model.desc4 isEqualToString:@"Apple"]); json = @"{\"id\":\"abcd\"}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue([model.modelID isEqualToString:@"abcd"]); json = @"{\"ext\":{\"id\":\"abcd\"}}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue([model.modelID isEqualToString:@"abcd"]); json = @"{\"id\":\"abcd\",\"ID\":\"ABCD\",\"Id\":\"Abcd\"}"; - model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json]; + model = [modelClass yy_modelWithJSON:json]; XCTAssertTrue([model.modelID isEqualToString:@"ABCD"]); jsonObject = [model yy_modelToJSONObject]; @@ -190,6 +206,16 @@ - (void)testCustom { XCTAssertTrue([jsonObject[@"ID"] isEqualToString:@"ABCD"]); } +- (void)testCustom1 { + YYTestPropertyMapperModelCustom1 *model = [YYTestPropertyMapperModelCustom1 new]; + [self _testCustomWithModel:model]; +} + +- (void)testCustom2 { + YYTestPropertyMapperModelCustom2 *model = [YYTestPropertyMapperModelCustom2 new]; + [self _testCustomWithModel:model]; +} + - (void)testWarn { NSString *json = @"{\"description\":\"Apple\",\"id\":12345}"; YYTestPropertyMapperModelWarn *model = [YYTestPropertyMapperModelWarn yy_modelWithJSON:json];