MAXJsonAdapter
模型对象 - JSON 序列化/反序列化
在 Objective-C 中将模型对象从JSON序列化或反序列化到JSON需要很多样板代码。MAXJsonAdapter 负责序列化和反序列化模型对象,无需特定的子类,只需 NSObject(默认基本对象类)即可。
让我们考虑一个简单的用户模型接口和实现
@interface User : NSObject
@property (nonatomic, strong) NSString *email;
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *middleName;
@property (nonatomic, strong) NSString *lastName;
@property (nonatomic, strong) NSString *phoneNumber;
@property (nonatomic, strong) NSArray <NSString *> *usernames;
-(id)initWithJsonDict:(NSDictionary *)dict;
@end
@implementation User
-(id)initWithJsonDict:(NSDictionary *)dict {
if(self = [super init]) {
_email = [dict objectForKey:@"email"];
_firstName = [dict objectForKey:@"firstName"];
_middleName = [dict objectForKey:@"middleName"];
_lastName = [dict objectForKey:@"lastName"];
_phoneNumber = [dict objectForKey:@"phoneNumber"];
_usernames = [dict objectForKey:@"usernames"];
}
return self;
}
@end
用户模型使用以下 json 数据序列化的字典初始化
{
"email" : "[email protected]",
"firstName" : "Bruce",
"lastName" : "Wayne",
"middleName": null,
"phoneNumber" : "0018008608889",
"usernames" : ["KillTheJoker", "RobinIsMyPet101", "TheRiddlerRiddlesTheRiddle"]
}
这是一个简化模型,其中没有日期或其他需要在运行时转换的值。对于此类行为,请参阅关于值转换器的章节。
我们不需要手动声明要从前面的字典中初始化哪些属性,可以使用 MAXJsonAdapter 来帮助我们,使用以下方法
User *user = [MAXJsonAdapter MAXJAObjectOfClass:[User class] delegate: nil fromDictionary: dict];
如果您已经有了一个模型对象并想使用一个字典来更新它,json 适配器也可以做到这一点
[MAXJsonAdapter refreshObject: object delegate: nil fromDictionary: dict]
这些方法将读取 User 模型上的所有属性名并将其与字典中的值相匹配。所有的用户属性都将被适当填充,从而使 initWithJsonDict: 方法变得冗余,可以被删除。我们也可以轻松地使用相同的信息更新模型。
如果您有模型对象,并想从它创建一个字典以发送回服务器或出于其他原因,可以使用以下方法
NSDictionary *objectDict = [MAXJsonAdapter MAXJADictFromObject: object delegate: nil];
当模型中的属性名与字典中的属性名不同,或者位于不同的字段中时,我们可以使用属性映射器将它们的数据从不同的字段中加载,或者将它们导出到不同的字段中的 json。有关属性映射的更多信息,请参阅下面的映射值章节。您可以通过遵循 MAXJsonAdapterDelegate 协议并传递一个委托参数到 MAXJsonAdapter 中来映射这些值。
我们在字典中经常从 APIs 收到不同格式的数据,例如日期,它们需要从 NSString 类型转换为 NSDate 类型,为此,您可以通过遵循 MAXJsonAdapterDelegate 协议并传递一个委托参数到 MAXJsonAdapter 中来使用值转换器。有关值转换器的更多信息,请参阅下面的章节。
映射值
让我们重新使用之前实现的用户类接口。但这次从服务器发送的 JSON 格式改变了,属性名也不再匹配。在这种情况下,我们使类遵循 MAXJsonAdapterDelegate 协议,并实现 MAJAPropertiesToMapObjectCreation 和 MAXJApropertiesToMapDictionaryCreation 以便在从对象创建 json 字典或从 json 字典创建对象时可以不同地映射属性。
@interface User : NSObject <MAXJsonAdapterDelegate>
@property (nonatomic, strong) NSString *email;
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *middleName;
@property (nonatomic, strong) NSString *lastName;
@property (nonatomic, strong) NSString *phoneNumber;
@property (nonatomic, strong) NSArray <NSString *> *usernames;
@end
新的用户 json 数据格式
{
"personInfo" : {
"firstName" : "Bruce",
"middleName" : null,
"lastName" : "Wayne"
},
"phoneNumber" : "7728282737"
"userEmail" : "[email protected]",
"aliases" : ["KillTheJoker", "RobinIsMyPet101", "TheRiddlerRiddlesTheRiddle"]
}
现在名称属性嵌入了一个名为 person info 的字段中,并且电子邮件和用户名已重命名为 userEmail 和 aliases。为了确保正确序列化属性,我们需要在 User 文件的实现中添加新的属性映射器。
@implementation User
-(NSArray <MAXJAPropertyMap *> *)MAXJAPropertiesToMapObjectCreation {
return @[[MAXJAPropertyMap MAXJAMapWithKey: @"firstName" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"personInfo" propertyMap: [MAXJAMapWithKey: @"firstName" propertyMap: nil]]],
[MAXJAPropertyMap MAXJAMapWithKey: @"middleName" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"personInfo" propertyMap: [MAXJAMapWithKey: @"middleName" propertyMap: nil]]],
[MAXJAPropertyMap MAXJAMapWithKey: @"lastName" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"personInfo" propertyMap: [MAXJAMapWithKey: @"lastName" propertyMap: nil]]],
[MAXJAPropertyMap MAXJAMapWithKey: @"email" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"userEmail" propertyMap: nil]],
[MAXJAPropertyMap MAXJAMapWithKey: @"usernames" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"aliases" propertyMap: nil]]
];
}
@end
在 MAXJAPropertyMap 对象的数组中,我们首先分配我们想要映射值的键的名称,例如 firstName,然后宣布我们想要在 json 中钻取的下一个键,即 personInfo,然后宣布获取值的下一个键是 firstName。当 MAXJsonAdapter 需要钻取 json 字段以查找某些键的值时,它将检查是否已声明属性映射器。对于电子邮件属性,我们只是声明一个新的映射到 userEmail 上,因为名称已更改,并且只映射名称,无需映射更深层次的 json。
为了使此工作,我们只需以下方式将包含委托的实例对象的实例传递给 MAXJsonAdapter
User *user = [MAXJsonAdapter MAXJAObjectOfClass: [User class] delegate: [[User alloc] init] fromDictionary: jsonDict];
您可以使任何 NSObject 遵循 MAXJsonAdapterDelegate 以映射值、转换它们并执行许多其他运行时任务。为了方便,我们将其放在了用户类中。
如果您需要创建一个 NSDictionary,并将其作为 json 发送到服务器,则 json 适配器还支持在从对象创建 NSDictionary 时映射值。我们再次使用 MAXJsonAdapterDelegate 协议并实现方法 MAXJAPropertiesToMapDictionaryCreation:
@implementation User
-(NSArray <MAXJAPropertyMap *> *)MAXJAPropertiesToMapDictionaryCreation {
return @[[MAXJAPropertyMap MAXJAMapWithKey: @"firstName" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"personInfo" propertyMap: [MAXJAMapWithKey: @"firstName" propertyMap: nil]]],
[MAXJAPropertyMap MAXJAMapWithKey: @"middleName" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"personInfo" propertyMap: [MAXJAMapWithKey: @"middleName" propertyMap: nil]]],
[MAXJAPropertyMap MAXJAMapWithKey: @"lastName" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"personInfo" propertyMap: [MAXJAMapWithKey: @"lastName" propertyMap: nil]]],
[MAXJAPropertyMap MAXJAMapWithKey: @"email" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"userEmail" propertyMap: nil]],
[MAXJAPropertyMap MAXJAMapWithKey: @"usernames" propertyMap: [MAXJAPropertyMap MAXJAMapWithKey: @"aliases" propertyMap: nil]]
];
}
使用此映射,我们将创建与从服务器接收的完全相同的 NSDictionary。我们可以使用以下方法从用户对象创建 NSDictionary
NSDictionary *userDict = [MAXJsonAdapter MAXJADictFromObject: user delegate: user];
这假设 User 对象遵循 MAXJsonAdapterDelegate 协议。这将创建如下格式的 json
新的用户 json 数据格式
{
"personInfo" : {
"firstName" : "Bruce",
"middleName" : null,
"lastName" : "Wayne"
},
"phoneNumber" : "7728282737"
"userEmail" : "[email protected]",
"aliases" : ["KillTheJoker", "RobinIsMyPet101", "TheRiddlerRiddlesTheRiddle"]
}
忽略或指定值
如果您想在从对象创建对象或字典时忽略某个属性,可以使用MAXJsonAdapterDelegate协议中的MAXJAPropertiesToIgnoreObjectCreation:方法或MAXJAPropertiesToIgnoreDictionaryCreation:方法来列出要忽略的属性。
以下是在User模型上的一个示例实现,该实现在创建其json时将忽略用户模型上的email属性。
@implementation User
-(NSArray <NSString *> *)MAXJAPropertiesToIgnoreDictionaryCreation {
return @[@"email"];
}
@end
通过向json适配器代理中适当的方法提供一个字符串数组,包含要忽略的属性名,您可以忽略任何想要的属性。
有时候,模型上有许多属性,您只想从中选择几个来创建字典。例如,您想更新用名,但后端只支持更新用户的第一、中间和姓氏。在这些情况下,声明您想要在从该对象创建对象或字典时使用的属性可能更有用。您可以通过实现MAXJsonAdapterDelegate协议中的MAXJAPropertiesForDictionaryCreation:或MAXJAPropertiesForObjectCreation:方法来实现。
以下是在User模型上的一个示例实现,其中我们只想创建包含用户名字的字典,不包含其他属性。
@implementation User
-(NSArray <NSString *> *)MAXJAPropertiesForDictionaryCreation {
return @[@"firstName", @"lastName", @"middleName"];
}
@end
当一个用户实例作为代理传递以创建json字典时,json将如下所示
{
"firstName" : "Bruce",
"middleName" : null,
"lastName" : "Wayne"
}
通过这种方法,您可以显着缩短需要声明的需要忽略的属性列表的数量。
请注意,如果您同时实现了要忽略的属性和声明的属性,则声明的属性将具有优先权,不会同时使用,只能使用其中一个。
值转换器
当创建模型对象时,通常需要转换一个属性,例如将String转换为日期,这是一个非常常见的用例。为了转换一个值,您首先需要使用MAXJAPropertyValueTransformers:协议方法声明您想使用的转换器。您还需要通过其适当的方法子类化MAXJAValueTransformer以转换该值。
MAXJAValueTransformer子类的示例实现,它将String转换为日期,然后在从对象创建NSDictionary时将日期转换回String。
static NSDateFormatter *_formatter = nil;
@implementation DateValueTransformer
+(NSDateFormatter *)formatter {
if(_formatter == nil) {
_formatter = [[NSDateFormatter alloc] init];
[_formatter setDateFormat:@"yyyy-MM-dd"];
}
return _formatter;
}
-(id)MAXJAObjectCreationFormat:(id)value {
if(value == nil) {
return nil;
}
if([value isKindOfClass: [NSString class]]) {
NSDate *date = [_formatter dateFromString: (NSString *)value];
return date;
}
return nil;
}
-(id)MAXJsonFormat:(id)value {
if(value == nil) {
return nil;
}
if([value isKindOfClass: [NSDate class]]) {
NSString *dateString = [_formatter stringFromDate: (NSDate*)value];
return dateString;
}
return nil;
}
@end
有两个方法需要实现:创建对象时要转换的值MAXJAObjectCreationFormat:和创建字典时要转换的值MAXJsonFormat:方法。
一旦实现这些方法,您需要在MAXJsonAdapterDelegate的MAXJAPropertyValueTransformers方法中将它们添加到中,在方法中,您声明要将值转换器用于哪些属性。在下面的模型中,我们有两个需要与值转换器相关联的日期属性。
@interface User : NSObject
@property (nonatomic, strong) NSString *email;
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *middleName;
@property (nonatomic, strong) NSString *lastName;
@property (nonatomic, strong) NSString *phoneNumber;
@property (nonatomic, strong) NSArray <NSString *> *usernames;
@property (nonatomic, strong) NSDate *createdAt;
@property (nonatomic, strong) NSDate *updatedAt;
@end
@implementation User
-(NSArray <MAXJAValueTransformer *> *)MAXJAPropertyValueTransformers {
return @[[DateValueTransformer MAXJAValueTransformerWithPropertyKeys: @[@"createdAt", @"updatedAt"]];
}
@end
在上面的实现中,createdAt和updatedAt属性将使用我们上面实现的类将值从字符串转换为日期,然后再从日期转换回字符串。如果属性有映射,只需在映射方法中声明它们,JSON适配器将负责其余工作。
子类化
通常,你会在类中将自己的类作为属性。使用MAXJsonAdapter序列化和反序列化子类化的属性非常容易。只需在MAXJsonAdapterDelegate约定对象中实现MAXJASubclassedProperties:方法。您还可以添加一个实例作为子类化属性的委托,以便可以使用适当的运行时转换。
添加子类化属性就像以下实现一样简单
@implementation User
-(NSArray <MAXJASublcassedProperty *> *)MAXJASubclassedProperties {
return @[[MAXJASubclassedProperty MAXJAPropertyKey: @"userInfo" class: [UserInfo class] delegate: nil];
}
@end
这就是添加子类化属性的全部内容。同样,与值转换器类似,子类化属性也支持使用委托中的属性映射方法进行属性映射。
要求
MAXJsonAdapter要求传递给它的模型对象是NSObject或符合键值编码的其他对象类型的子类。
与其他需要子类化的JSON适配器不同,此适配器不需要特定的子类化,因此可以轻松使用需要子类化的其他库。
安装
MAXJsonAdapter可通过CocoaPods获取。要安装,请将以下行添加到您的Podfile中
pod 'MAXJsonAdapter'
作者
roodoodey,Mathieu Grettir Skúlason。
许可证
MAXJsonAdapter可供MIT许可证使用。有关更多信息,请参阅LICENSE文件。