OCMapper 版本 2.1

OCMapper 版本 2.1

测试已测试
Lang语言 Obj-CObjective C
许可证 MIT
Released最后发布2015年6月

Aryan Ghassemi维护。



OCMapper 版本 2.1

  • 作者:
  • Aryan Ghassemi

Objective-C 数据映射库

Build Status

OCMapper 是一个 Objective-C 数据映射库,可以将 NSDictionary 转换为 NSObject。我编写 OCMapper 的灵感是想实现以下两点:

  • 通过网络服务简化/自动化数据检索
  • 避免将解析逻辑添加到模型对象中(我是职责分离的忠实粉丝!)

Swift 支持

OCMapper 利用 Objective-C 运行时 API,只与从 NSObject 继承的类一起工作

@objc public class User: NSObject {

    var id: String?
    var name: String?
    var category: String?
    var count: NSNumber?
    var address: Address?
    var photos: [Photo]?
}

var user = ObjectMapper.sharedInstance().objectFromSource(json, toInstanceOfClass: User.self) as User?

Alamofire 扩展

https://github.com/Alamofire/Alamofire

public extension Request {

    public func responseObjects<T: NSObject> (type: T.Type, completion: (NSURLRequest, NSURLResponse?, [T]?, NSError?)->()) -> Self {

        return response(serializer: Request.JSONResponseSerializer(options: .AllowFragments)) { request, response, json, error in

            if let error = error {
                completion(request, response, nil, error)
            }
            else {
                let objects = ObjectMapper.sharedInstance().objectFromSource(json, toInstanceOfClass: type) as? [T]
                completion(request, response, objects, nil)
            }
        }
    }

    public func responseObject<T: NSObject> (type: T.Type, completion: (NSURLRequest, NSURLResponse?, T?, NSError?)->()) -> Self {

        return response(serializer: Request.JSONResponseSerializer(options: .AllowFragments)) { request, response, json, error in

            if let error = error {
                completion(request, response, nil, error)
            }
            else {
                let object = ObjectMapper.sharedInstance().objectFromSource(json, toInstanceOfClass: type) as? T
                completion(request, response, object, nil)
            }
        }
    }

}

扩展使用

let request = Manager.sharedInstance.request(requestWithPath("example.com/users", method: .GET, parameters: nil))

request.responseObjects(User.self) { request, response, users, error in
    // users is an array of User objects
}


let request = Manager.sharedInstance.request(requestWithPath("example.com/users/5", method: .GET, parameters: nil))

request.responseObject(User.self) { request, response, user, error in
    // user is an instance of User
}

功能

  • 支持数组映射
  • 支持树形结构映射
  • 支持复杂数据嵌套
  • 支持 Core Data (NSManagedObject)
  • 映射配置可以在代码中或通过 PLIST 进行
  • 根据 NSDictionary 键自动检测键/值
  • 完全可配置
  • 不需要对模型进行子类化或添加任何其他代码
  • 自动日期转换,可配置日期格式化工具

示例

@interface User
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, strong) NSDate *dateOfBirth;
@property (nonatomic, strong) NSDate *accountCreationDate;
@property (nonatomic, strong) Address *address;
@property (nonatomic, strong) NSMutableArray *posts;
@end

@interface Address
@property (nonatomic, strong) NSString *city;
@property (nonatomic, strong) NSString *country;
@end

@interface Post
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) User *author;
@property (nonatomic, strong) NSDate *datePosted;
@end

简单自动映射

{
   "firstName"   : "Aryan",
   "lastName"    : "Ghassemi",
   "age"         : 26,
   "dateOfBirth" : "01/01/2013"
}

User *user = [User objectFromDictionary:aDictionary];

嵌套自动映射

在这种情况下,所有键/值都是自动映射的,因为它们都是相似的。 "address" 会自动转换为 "Address" 对象。 "posts" 会自动转换为 "Post" 对象数组。库会检测复数名词,并找到用于映射的正确类。

{
   "firstName"   : "Aryan",
   "lastName"    : "Ghassemi",
   "age"         : 26,
   "dateOfBirth" : "01/01/2013",
   "address"     : { 
                        "city" : "San Diego", 
                        "country" : "US"  
                   },
   "posts"       : [
                         {
                             "title" : "Post 1 title",
                             "datePosted : "04/15/2013",
                         },
                         {
                             "title" : "Post 2 title",
                             "datePosted : "04/12/2013",
                         }
                   ]
}

User *user = [User objectFromDictionary:aDictionary];

复杂映射

这是一个更复杂的场景,其中字典键不匹配模型属性名称。出生日期的键从 "dateOfBirth" 更改为 "dob"。每篇帖子都有一个作者,并且用于转换的类(User)没有具有该名称的属性。

{
   "firstName"   : "Aryan",
   "lastName"    : "Ghassemi",
   "age"         : 26,
   "dob" : "01/01/2013",
   "address"     : { 
                        "city" : "San Diego", 
                        "country" : "US"  
                   },
   "posts"       : [
                         {
                             "title" : "Post 1 title",
                             "datePosted : "04/15/2013",
                             "author" : { 
                                             "firstName" : "Chuck", 
                                             "lastName" : "Norris" 
                                        }
                         },
                         {
                             "title" : "Post 2 title",
                             "datePosted : "04/12/2013",
                             "author" : { 
                                             "firstName" : "Chuck", 
                                             "lastName" : "Norris" 
                                        }
                         }
                   ]
}

// Handle different key for dateOfBirth
[inCodeMappingProvider mapFromDictionaryKey:@"dob" toPropertyKey:@"dateOfBirth" forClass:[User class]];

// Handle conversion of "author" to a "User" object
// Mapping would NOT be required if both dictionary an drpopery were named 'user'
[inCodeMappingProvider mapFromDictionaryKey:@"author" toPropertyKey:@"author" withObjectType:[User class] forClass:[Comment class]];

User *user = [User objectFromDictionary:aDictionary];

在根级别映射数组

[
   {
      "firstName" : "Aryan",
      ... rest of JSON data ...
   },
   {
      "firstName" : "Chuck",
      ... rest of JSON data ...
   },
]

NSArray *users = [User objectFromDictionary:aDictionary];

扁平数据到复杂对象

为了提高性能,默认情况下不再启用此功能。在ObjectMapper类中有一个名为normalizeDictionary的属性,允许您在需要时打开此功能。

{
      "firstName"           : "Aryan",
      "city"                : "San Diego"
      "country"             : "United States"
}

   // We map city and country to a nested object called 'address' inside the 'user' object
    [self.mappingProvider mapFromDictionaryKey:@"city" toPropertyKey:@"address.city" forClass:[User class]];
    [self.mappingProvider mapFromDictionaryKey:@"country" toPropertyKey:@"address.country" forClass:[User class]];

    User *user = [User objectFromDictionary:aDictionary];
      NSLog(@"FirstName:%@   City:%@  Country:%@", 
      user.firstName,
      user.address.city, 
      user.address.coutnry);

日期转换

Automapper有一个名为defaultDateFormatter的属性,当设置该属性时,将使用此NSDateFormatter对所有NSDate属性进行日期转换。为了最佳性能,建议设置defaultDateFormatter。请注意,自定义dateFormatters比默认dateFormatters有优先级。

[inCodeMappingProvider setDefaultDateFormatter:aDefaultDateFormatter];

ObjectMapper使用一组通用的NSDateFormatters将字符串转换为NSDate。以下是默认支持的常见日期格式的列表

"yyyy-MM-dd"
"MM/dd/yyyy"
"yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZ"
"yyyy-MM-dd HH:mm:ss"
"MM/dd/yyyy HH:mm:ss aaa"
"yyyy-MM-dd'T'HH:mm:ss'Z'"

您还可以为特定于类和属性的NSDateFormatter定义自定义格式

注意:只要所有日期格式都是标准的,或者默认dateFormatters知道如何解析日期,则无需设置DateFormatter。下面的代码只是为了演示有可能设置自定义dateFormatters,但在本例中,由于使用了标准日期格式,因此这不是必需的。

{
      "firstName"           : "Aryan",
      "accountCreationDate" : "01/21/2005"
      "dateOfBirth"         : "2005-21-01"
}

// Custom formatter for account creation date
NSDateFormatter *accountCreationFormatter = [[NSDateFormatter alloc] init];
[accountCreationFormatter setDateFormat:@"MM/dd/yyyy"];
[inCodeMappingProvider setDateFormatter:accountCreationFormatter forProperty:@"accountCreationDate" andClass:[User class]];

// Custom formatter for date of birth
NSDateFormatter *dateOfBirthFormatter = [[NSDateFormatter alloc] init];
[dateOfBirthFormatter setDateFormat:@"yyyy-dd-MM"];
[inCodeMappingProvider setDateFormatter:dateOfBirthFormatter forProperty:@"dateOfBirth" andClass:[User class]];

User *user = [User objectFromDictionary:aDictionary];

数据转换器

数据转换器允许您捕获映射的一部分,并手动映射它。它还打开了多态映射的空间。

将字段转换为另一个

{
   "firstName" : "Aryan",
   "country" : "United States"
}

@implementation User
@property(nonatomic, strong) NSString *firstName;
@property(nonatomic, strong) Country *country;
@end

[mappingProvider mapFromDictionaryKey:@"country" toPropertyKey:@"country" forClass:[User class] withTransformer:^id(id currentNode, id parentNode) {
    return [[Country alloc] initWithName:currentNode];
}];

使用转换器进行多态关系

{
   "firstName" : "Aryan",
   "vehicleType" : "car",
   "vehicle" : { /*specific product Info*/ },
}

@implementation User
@property(nonatomic, strong) NSString *firstName;
@property(nonatomic, strong) Vehicle *vehicle;
@end

[mappingProvider mapFromDictionaryKey:@"vehicle" toPropertyKey:@"vehicle" forClass:[User class] withTransformer:^id(id currentNode, id parentNode) {
    NSString *productType = [parentNode objectForKey:@"vehicleType"];
    Vehicle *vehicle;

    if ([productType isEqual:@"car"])
    {
        vehicle = [Car objectFromDictionary:currentNode];
    }
    else if ([productType isEqual:@"bike"])
    {
        vehicle = [Bike objectFromDictionary:currentNode];
    }


    return vehicle;
}];

// Or event better

[mappingProvider mapFromDictionaryKey:@"vehicle" toPropertyKey:@"vehicle" forClass:[User class] withTransformer:^id(id currentNode, id parentNode) {

    NSString *productType = [parentNode objectForKey:@"vehicleType"];
    Class class = NSClassFromString(productType.capitalizedString);
    return [class objectFromDictionary:currentNode];
}];

转换器的其他可能性

{
   "firstName" : "Aryan",
   "image" : "BASE64_ENCODED_STRING"
}

@implementation User
@property(nonatomic, strong) NSString *firstName;
@property(nonatomic, strong) UIImage *image;
@end

[mappingProvider mapFromDictionaryKey:@"image" toPropertyKey:@"image" forClass:[User class] withTransformer:^id(id currentNode, id parentNode) {

    return [UIImage imageFromBase64String:currentNode];
}];

逆映射

逆映射是指将对象映射到字典。这与标准字典到属性映射非常相似。以下方法可以用于设置针对对象到字典映射的定制映射。

- (void)mapFromPropertyKey:(NSString *)propertyKey toDictionaryKey:(NSString *)dictionaryKey forClass:(Class)class;
- (void)mapFromPropertyKey:(NSString *)propertyKey toDictionaryKey:(NSString *)dictionaryKey forClass:(Class)class withTransformer:(MappingTransformer)transformer;
- (void)setDateFormatter:(NSDateFormatter *)dateFormatter forDictionary:(NSString *)property andClass:(Class)class;

InCodeMappingProvider类有一个名为automaticallyGenerateInverseMapping的属性。默认情况下,此属性设置为true,这意味着每当设置字典到属性的映射时,将自动生成逆映射,因此无需手动编写对象到字典映射的映射。唯一例外是,使用数据转换器映射字典到属性不能自动反转。

当automaticallyGenerateInverseMapping设置为true时,还会为逆关系创建date formatters。

Core Data支持

要使用core data,您可以向ObjectMapper添加一个ManagedObjectInstanceProvider

ManagedObjectInstanceProvider *instanceProvider = [[ManagedObjectInstanceProvider alloc] initWithManagedObjectContext:moc];

[[ObjectMapper sharedInstance] addInstanceProvider:instanceProvider];

默认情况下,Object mapper在每次映射时都会创建新的NSManagedObject实例。为了更新现有记录,您可以提供给定类的唯一键,Object mapper将自动更新现有记录。

[managedObjectInstanceProvider setUniqueKeys:@[@"userId"] forClass:[User class] withUpsertMode:UpsertModePurgeExistingObject];

为类分配键时,OCMapper还要求一个枚举来描述Object mapper应该如何更新现有记录。

  • UpsertModeUpdateExistingObject:此选项创建一个新的临时管理对象实例,然后根据提供的键尝试找到现有记录。如果找到一条记录,它将更新现有托管对象的全部属性,最后删除临时对象。在启用此upsert模式时,不会调用任何相关托管对象的删除操作,因此记录将保留在内存中。例如,如果用户的地址从A改为B,地址将正确更新,但两条记录都将保留在core data中。

  • UpsertModePurgeExistingObject: 此选项会创建一个新的管理对象实例,然后基于给定的键尝试找到现有的记录。如果找到一条记录,则会调用该记录的删除操作,然后将新创建的对象插入上下文。使用此upsert模式时,将调用现有管理对象的删除操作,因此核心数据会删除所有相关的关联对象,并且在核心数据模型中应用所有的“删除规则”。例如,如果用户的信息被更新,电话号码从A变为B,并且如果删除规则设置为级联,则将移除地址A并将地址B分配给用户。

不同用法及辅助工具

// Using ObjectMapper Directly
ObjectMapper *mapper = [[ObjectMapper alloc] init];
Urse *user = [mapper objectFromSource:dictionary toInstanceOfClass:[User class]];

// Using ObjectMapper Singleton Instance
Urse *user = [[ObjectMapper sharedInstance] objectFromSource:dictionary toInstanceOfClass:[User class]];

为了使用这些类别,您必须将映射提供程序添加到单例实例中

// Using NSObject Category
User *user = [User objectFromDictionary:aDictionary];

// Using NSDictionary Category
User *user = [aDictionary objectForClass:[User class]];

变更日志

2.0

修复了在1.8版本中引入的错误,其中两个单词的类名没有自动映射。例如(sessionUser会尝试映射到Sessionuser类,而不是SessionUser,并会失败映射)

1.9

自动执行NSString到NSNumber和NSNumber到NSString的转换

1.8

  • 初始化时不再将bundle类名加载到内存中
  • 针对#22进行修复
  • 如果键/属性名称匹配(例如,“location”键会自动映射到“location”属性,即使类类型名为“Address”),则不再需要为非数组指针编写映射
  • 为映射swift类编写了测试

注意

自动映射将嵌套数组区分对待。ObjectMapper在检查转换类之前会将对字典键的第一个字母大写。

  • "UserAddress"将自动映射到"UserAddress"
  • "userAddress"将自动映射到"UserAddress"
  • "useraddress"将不会映射到"UserAddress",您需要手动编写映射