NSJSONSerialization 到普通的旧对象?
NSJSONSerialization to plain old object?
许多现代编程语言都有 JSON 支持编码和解码的库 json to/from“普通旧对象”——即主要只是 类 的实例具有数据属性(属性可以是普通类型 de/encoded 或其他普通的旧对象)。示例包括 Google 的 GSON,golang 的 encoding/json
和其他。
有没有类似Objective-C的东西?
我知道可以枚举 Objective-C 类 的属性,并且有人会使用该功能创建 JSON“bean 映射器”似乎是合理的,但是 Google 搜索对我没有产生任何结果,除了 this blog post on Apple's Swift website 展示了如何手动反序列化 JSON 为“模型对象”以及为什么他们认为自动执行此操作(DRYing 代码)是一个错误想法。
这是我的解决方案,它不是基于库——因为我找不到任何库——而是使用 Foundation 和 Objective-C 运行时方法——如上面的评论所述:
#import <objc/runtime.h>
NSArray<NSString*>* classPropertyList(id instance) {
NSMutableArray* propList = [NSMutableArray array];
unsigned int numProps = 0;
objc_property_t* props = class_copyPropertyList(object_getClass(instance), &numProps);
for (int i = 0; i < numProps; i++)
[propList addObject:[NSString stringWithUTF8String:property_getName(props[i])]];
free(props);
return propList;
}
NSString* typeOfProperty(Class clazz, NSString* propertyName) {
objc_property_t prop = class_getProperty(clazz, [propertyName UTF8String]);
NSArray<NSString*>* propAttrs = [[NSString stringWithUTF8String:property_getAttributes(prop)] componentsSeparatedByString:@","];
if ([(propAttrs[0]) hasPrefix:@"T@\""])
return [propAttrs[0] componentsSeparatedByString:@"\""][1];
return nil;
}
@implementation JSONMarshallable
- (NSData*)toJSON {
return [self toJSON:self withNullValues:YES];
}
- (NSString*)toJSONString {
return [self toJSONString:self withNullValues:YES];
}
- (NSData*)toJSON:_ withNullValues:(bool)nullables {
NSError* error;
NSDictionary* dic = [self toDictionary:self withNullValues:nullables];
NSData* json = [NSJSONSerialization dataWithJSONObject:dic options:0 error:&error];
if (!json) {
NSLog(@"Error encoding DeviceConfigurationRequest: %@", error);
return nil;
}
return json;
}
- (NSString*) toJSONString:_ withNullValues:(bool)nullables {
NSData* json = [self toJSON:self withNullValues:nullables];
return [[NSString alloc] initWithBytes:[json bytes] length:[json length] encoding:NSUTF8StringEncoding];
}
- (NSDictionary*)toDictionary:_ withNullValues:(bool)nullables {
NSMutableDictionary* dic = [NSMutableDictionary new];
for (id propName in classPropertyList(self)) {
id val = [self valueForKey:propName];
if (!nullables && (val == nil || val == NSNull.null))
continue;
if ([val respondsToSelector:@selector(toDictionary:withNullValues:)])
val = [val toDictionary:val withNullValues:nullables];
[dic setObject:(val == nil ? NSNull.null : val) forKey:propName];
}
return dic;
}
- (instancetype)initWithJSONString:(NSString*)json {
return [self initWithJSON:[json dataUsingEncoding:NSUTF8StringEncoding]];
}
- (instancetype)initWithJSON:(NSData*)json {
NSError* error;
if (json == nil)
return nil;
NSDictionary* dataValues = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error];
if (!dataValues) {
NSLog(@"Error parsing invalid JSON for %@: %@", NSStringFromClass(object_getClass(self)), error);
return nil;
}
return [self initWithDictionary:dataValues];
}
- (instancetype)initWithDictionary:(NSDictionary*)dataValues {
if (dataValues == nil)
return nil;
if (self = [super init])
for (id key in dataValues) {
id val = [dataValues objectForKey:key];
if (![self respondsToSelector:NSSelectorFromString(key)])
continue;
NSString* typeName = typeOfProperty([self class], key);
if ([val isKindOfClass:[NSNull class]]) { // translate NSNull values to something useful, if we can
if (typeName == nil)
continue; // don't try to set nil to non-pointer fields
val = nil;
} else if ([val isKindOfClass:[NSDictionary class]] && typeName != nil)
val = [[NSClassFromString(typeName) alloc] initWithDictionary:val];
[self setValue:val forKey:key];
}
return self;
}
@end
然后通过从 JSONMarshallable
继承来创建自定义模型对象很容易,就像这样:
model.h
:
#import "JSONMarshallable.h"
@interface MyModel : JSONMarshallable
@property NSString* stringValue;
@property NSNumber* numericValue;
@property bool boolValue;
@end
model.m
:
@implementation MyModel
@end
SomeThingElse.m
:
// ...
NSData* someJson;
MyModel* obj = [[MyModel alloc] initWithJSON:someJson];
NSString* jsonObj = [obj toJSONString:nil withNullValues:NO];
欢迎批评! (我不是很擅长Objective C,可能犯了很多失礼)
问题:
- 我可以使用
NSNumber*
处理可为空的数字(尽管 C 基元可以很好地处理 non-nullable 数字),但我不知道如何表示可为空的布尔值 - 即一个可选的字段使用 withNullValues:NO
. 时未编码
发送没有属性的字段(例如,我使用的服务器在 snake-case 和 underscrore-case 中发送值以便于解析)抛出exception.(通过使用 respondsToSelector:
和 setValue:
而不是 setValuesForKeysWithDictionary:
解决)。
尝试将 nil
值设置为 primitive-typed 字段会导致异常。(通过检查 属性 类型和 NSNull
).
对嵌套对象根本不起作用 - 即具有自定义模型对象属性的自定义模型对象。(通过检查 属性 类型解决并递归 encoding/decoding).
- 可能不能很好地处理数组 - 我的软件中还不需要这些,所以我没有实现适当的支持(尽管我验证了编码简单字符串数组的效果很好)。
许多现代编程语言都有 JSON 支持编码和解码的库 json to/from“普通旧对象”——即主要只是 类 的实例具有数据属性(属性可以是普通类型 de/encoded 或其他普通的旧对象)。示例包括 Google 的 GSON,golang 的 encoding/json
和其他。
有没有类似Objective-C的东西?
我知道可以枚举 Objective-C 类 的属性,并且有人会使用该功能创建 JSON“bean 映射器”似乎是合理的,但是 Google 搜索对我没有产生任何结果,除了 this blog post on Apple's Swift website 展示了如何手动反序列化 JSON 为“模型对象”以及为什么他们认为自动执行此操作(DRYing 代码)是一个错误想法。
这是我的解决方案,它不是基于库——因为我找不到任何库——而是使用 Foundation 和 Objective-C 运行时方法——如上面的评论所述:
#import <objc/runtime.h>
NSArray<NSString*>* classPropertyList(id instance) {
NSMutableArray* propList = [NSMutableArray array];
unsigned int numProps = 0;
objc_property_t* props = class_copyPropertyList(object_getClass(instance), &numProps);
for (int i = 0; i < numProps; i++)
[propList addObject:[NSString stringWithUTF8String:property_getName(props[i])]];
free(props);
return propList;
}
NSString* typeOfProperty(Class clazz, NSString* propertyName) {
objc_property_t prop = class_getProperty(clazz, [propertyName UTF8String]);
NSArray<NSString*>* propAttrs = [[NSString stringWithUTF8String:property_getAttributes(prop)] componentsSeparatedByString:@","];
if ([(propAttrs[0]) hasPrefix:@"T@\""])
return [propAttrs[0] componentsSeparatedByString:@"\""][1];
return nil;
}
@implementation JSONMarshallable
- (NSData*)toJSON {
return [self toJSON:self withNullValues:YES];
}
- (NSString*)toJSONString {
return [self toJSONString:self withNullValues:YES];
}
- (NSData*)toJSON:_ withNullValues:(bool)nullables {
NSError* error;
NSDictionary* dic = [self toDictionary:self withNullValues:nullables];
NSData* json = [NSJSONSerialization dataWithJSONObject:dic options:0 error:&error];
if (!json) {
NSLog(@"Error encoding DeviceConfigurationRequest: %@", error);
return nil;
}
return json;
}
- (NSString*) toJSONString:_ withNullValues:(bool)nullables {
NSData* json = [self toJSON:self withNullValues:nullables];
return [[NSString alloc] initWithBytes:[json bytes] length:[json length] encoding:NSUTF8StringEncoding];
}
- (NSDictionary*)toDictionary:_ withNullValues:(bool)nullables {
NSMutableDictionary* dic = [NSMutableDictionary new];
for (id propName in classPropertyList(self)) {
id val = [self valueForKey:propName];
if (!nullables && (val == nil || val == NSNull.null))
continue;
if ([val respondsToSelector:@selector(toDictionary:withNullValues:)])
val = [val toDictionary:val withNullValues:nullables];
[dic setObject:(val == nil ? NSNull.null : val) forKey:propName];
}
return dic;
}
- (instancetype)initWithJSONString:(NSString*)json {
return [self initWithJSON:[json dataUsingEncoding:NSUTF8StringEncoding]];
}
- (instancetype)initWithJSON:(NSData*)json {
NSError* error;
if (json == nil)
return nil;
NSDictionary* dataValues = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error];
if (!dataValues) {
NSLog(@"Error parsing invalid JSON for %@: %@", NSStringFromClass(object_getClass(self)), error);
return nil;
}
return [self initWithDictionary:dataValues];
}
- (instancetype)initWithDictionary:(NSDictionary*)dataValues {
if (dataValues == nil)
return nil;
if (self = [super init])
for (id key in dataValues) {
id val = [dataValues objectForKey:key];
if (![self respondsToSelector:NSSelectorFromString(key)])
continue;
NSString* typeName = typeOfProperty([self class], key);
if ([val isKindOfClass:[NSNull class]]) { // translate NSNull values to something useful, if we can
if (typeName == nil)
continue; // don't try to set nil to non-pointer fields
val = nil;
} else if ([val isKindOfClass:[NSDictionary class]] && typeName != nil)
val = [[NSClassFromString(typeName) alloc] initWithDictionary:val];
[self setValue:val forKey:key];
}
return self;
}
@end
然后通过从 JSONMarshallable
继承来创建自定义模型对象很容易,就像这样:
model.h
:
#import "JSONMarshallable.h"
@interface MyModel : JSONMarshallable
@property NSString* stringValue;
@property NSNumber* numericValue;
@property bool boolValue;
@end
model.m
:
@implementation MyModel
@end
SomeThingElse.m
:
// ...
NSData* someJson;
MyModel* obj = [[MyModel alloc] initWithJSON:someJson];
NSString* jsonObj = [obj toJSONString:nil withNullValues:NO];
欢迎批评! (我不是很擅长Objective C,可能犯了很多失礼)
问题:
- 我可以使用
NSNumber*
处理可为空的数字(尽管 C 基元可以很好地处理 non-nullable 数字),但我不知道如何表示可为空的布尔值 - 即一个可选的字段使用withNullValues:NO
. 时未编码
发送没有属性的字段(例如,我使用的服务器在 snake-case 和 underscrore-case 中发送值以便于解析)抛出exception.(通过使用respondsToSelector:
和setValue:
而不是setValuesForKeysWithDictionary:
解决)。尝试将(通过检查 属性 类型和nil
值设置为 primitive-typed 字段会导致异常。NSNull
).对嵌套对象根本不起作用 - 即具有自定义模型对象属性的自定义模型对象。(通过检查 属性 类型解决并递归 encoding/decoding).- 可能不能很好地处理数组 - 我的软件中还不需要这些,所以我没有实现适当的支持(尽管我验证了编码简单字符串数组的效果很好)。