提问人:Guss 提问时间:4/28/2022 最后编辑:Guss 更新时间:7/6/2022 访问量:232
NSJSON将普通的旧对象化?
NSJSONSerialization to plain old object?
问:
许多现代编程语言都有 JSON 库,这些库支持对 json 进行编码和解码,这些库支持将 json 编码到/从“普通旧对象”进行编码 - 即主要只具有数据属性的类的实例(属性可以是可以简单解码/编码的类型,也可以是其他普通的旧对象)。例子包括谷歌的GSON、golang的等。encoding/json
有没有类似于 Objective-C 的东西?
我知道可以枚举 Objective-C 类的属性,并且有人会使用该功能来创建 JSON“bean 映射器”似乎是合理的,但谷歌搜索对我来说没有产生任何结果,除了 Apple Swift 网站上的这篇博文展示了如何手动反序列化 JSON 到“模型对象”以及为什么他们认为自动执行此操作(DRYing 代码)是一个坏主意 (*)。
*) 原因基本上是,不需要编写大量样板(他们的示例实现是 36 LoC 来解析 3 个属性)并不是一个显着的改进,并且构建几个可选的回调来允许数据验证是很困难的。我显然不同意所有这些。
答:
1赞
Guss
4/30/2022
#1
这是我的解决方案,它不是基于库 - 因为我找不到任何库 - 而是使用 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];
欢迎评论家!(我不是很擅长目标C,可能做了很多失礼🤭)
问题:
- 我可以处理可为空的数字(尽管 C 原语适用于不可为空的数字),但我不知道如何表示可为空的布尔值 - 即在使用 .
NSNumber*
withNullValues:NO
- 发送
没有属性的字段(例如,我使用的服务器同时发送蛇形大小写和 underscrore-case 的值以使其易于解析)会引发异常。(通过使用 和 代替 来解决)。respondsToSelector:
setValue:
setValuesForKeysWithDictionary:
尝试将(通过检查属性类型和 解决)。nil
值设置为基元类型字段会导致异常。NSNull
- 完全
不适用于嵌套对象 - 即具有也是自定义模型对象的属性的自定义模型对象。(通过检查属性类型和递归编码/解码来解决)。 - 可能不能很好地处理数组 - 我的软件中还没有需要这些数组,所以我没有实现适当的支持(尽管我验证了编码简单的字符串数组效果很好)。
评论
NSObject::setValuesForKeysWithDictionary
NSObject::dictionaryWithValuesForKeys
NSData
NSJSONSerialization
setValuesForKeysWithDictionary
setValue:forKey: