通过 RestKit 将 JSON 的本地 NSString 反序列化为对象(无网络下载)

Posted

技术标签:

【中文标题】通过 RestKit 将 JSON 的本地 NSString 反序列化为对象(无网络下载)【英文标题】:Deserializing local NSString of JSON into objects via RestKit (no network download) 【发布时间】:2012-01-15 21:42:47 【问题描述】:

是否可以通过 RestKit 将 JSON 的 NSString 反序列化为对象?我检查了 API 列表 here 并找不到可以用于此目的的东西。我能找到的最接近的是在解析输入后返回NSDictionary 的各种解析器类。我假设 RestKit 在下载响应后使用这些解析器,所以我的想法是该功能在 RestKit 中的某个地方可用,但没有公开。

如果我没有遗漏任何东西并且没有公开此功能,还有什么替代方案?两个明显的看起来不太有希望:获取结果 NSDictionary 并尝试反序列化自己(有效地重新实现 RestKit)或尝试深入研究 RestKit 源代码,看看是否可以以某种方式暴露(看起来乏味且容易出错)。

提前感谢您的帮助。

PS:这个想法是反序列化对象上的字符串属性实际上是另一组对象的 JSON 表示(在某种意义上是嵌入的 JSON),并且在运行时按需反序列化。

【问题讨论】:

【参考方案1】:

相当“简单”:

NSString *stringJSON;
...

RKJSONParserJSONKit *parser;
NSError *error= nil;
parser= [[[RKJSONParserJSONKit alloc] init] autorelease]; 
MyManagedObject *target;
target= [MyManagedObject object];

NSDictionary *objectAsDictionary;
RKObjectMapper* mapper;
objectAsDictionary= [parser objectFromString:stringJSON error:&error];
mapper = [RKObjectMapper mapperWithObject:objectAsDictionary 
                          mappingProvider:[RKObjectManager sharedManager].mappingProvider];
mapper.targetObject = target;
RKObjectMappingResult* result = [mapper performMapping];
NSLog(@"%@", [result asObject]);

【讨论】:

【参考方案2】:

截至RestKit 0.20.0-pre2

NSString* JSONString = @" \"name\": \"The name\", \"number\": 12345";
NSString* MIMEType = @"application/json";
NSError* error;
NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:MIMEType error:&error];
if (parsedData == nil && error) 
    // Parser error...


AppUser *appUser = [[AppUser alloc] init];

NSDictionary *mappingsDictionary = @ @"someKeyPath": someMapping ;
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingsDictionary];
mapper.targetObject = appUser;
NSError *mappingError = nil;
BOOL isMapped = [mapper execute:&mappingError];
if (isMapped && !mappingError) 
    // Yay! Mapping finished successfully
    NSLog(@"mapper: %@", [mapper representation]);
    NSLog(@"firstname is %@", appUser.firstName);

【讨论】:

您知道如何从已配置的 RKObjectManager 中执行此操作吗? 这是您要找的吗? github.com/RestKit/…【参考方案3】:

这适用于 Restkit 0.21.0:

NSString* jsonFilePath = [[NSBundle mainBundle] pathForResource:@"fileName"
                                                 ofType:@"json"];

NSString* JSONString = [NSString stringWithContentsOfFile:jsonFilePath
                                              encoding:NSUTF8StringEncoding
                                                 error:NULL];


NSError* error;
NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:&error];
if (parsedData == nil && error) 
    // Parser error...


//_objectManager is RKObjectManager instance
NSMutableDictionary *mappingsDictionary = [[NSMutableDictionary alloc] init];
for (RKResponseDescriptor *descriptor in _objectManager.responseDescriptors) 
    [mappingsDictionary setObject:descriptor.mapping forKey:descriptor.keyPath];


RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingsDictionary];
NSError *mappingError = nil;
BOOL isMapped = [mapper execute:&mappingError];
if (isMapped && !mappingError) 
    NSLog(@"result %@",[mapper mappingResult]);

【讨论】:

【参考方案4】:

这适用于 Restkit 0.20,使用 核心数据实体。它基于@innerself 给出的解决方案

NSString* jsonFilePath = [[NSBundle mainBundle] pathForResource:@"info-base"
                                                         ofType:@"json"];

NSString* JSONString = [NSString stringWithContentsOfFile:jsonFilePath
                                                 encoding:NSUTF8StringEncoding
                                                    error:NULL];


NSError *error = nil;

NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:&error];
if (parsedData == nil && error) 
    // Parser error...
    NSLog(@"parse error");


//_objectManager is RKObjectManager instance
NSMutableDictionary *mappingsDictionary = [[NSMutableDictionary alloc] init];
for (RKResponseDescriptor *descriptor in [RKObjectManager sharedManager].responseDescriptors) 

    [mappingsDictionary setObject:descriptor.mapping forKey:descriptor.keyPath];


RKManagedObjectMappingOperationDataSource *datasource = [[RKManagedObjectMappingOperationDataSource alloc]
                                                         initWithManagedObjectContext:[RKManagedObjectStore defaultStore].persistentStoreManagedObjectContext
                                                                                cache:[RKManagedObjectStore defaultStore].managedObjectCache];

RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData
                                                           mappingsDictionary:mappingsDictionary];
[mapper setMappingOperationDataSource:datasource];

NSError *mappingError = nil;
BOOL isMapped = [mapper execute:&mappingError];
if (isMapped && !mappingError) 
    // data is in [mapper mappingResult]

【讨论】:

【参考方案5】:

您可以在 RKManagedObjectResponseMapperOperation 类中看到 RestKit 是如何在内部执行此操作的。

此操作分为三个阶段。

首先是将JSON字符串解析成NSDictionarys、NSArrays等,这是最简单的部分。

id parsedData = [RKMIMETypeSerialization objectFromData:data
                                               MIMEType:RKMIMETypeJSON
                                                  error:error];

接下来,您需要运行映射操作来将此数据转换为您的 NSManagedObjects。这个有点牵强。

__block NSError *blockError = nil;
__block RKMappingResult *mappingResult = nil;
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 1;

[[RKObjectManager sharedManager].managedObjectStore.persistentStoreManagedObjectContext performBlockAndWait:^

记得用你自己的映射替换这个字典。键 [NSNull null] 从根映射此对象。

    NSDictionary *mappings = @[NSNull null]: [jotOfflineRequestStatus mapping];

    RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData
                                                               mappingsDictionary:mappings];

    RKManagedObjectMappingOperationDataSource *dataSource = [[RKManagedObjectMappingOperationDataSource alloc]
                                                             initWithManagedObjectContext:[RKManagedObjectStore defaultStore].persistentStoreManagedObjectContext
                                                             cache:[RKManagedObjectStore defaultStore].managedObjectCache];
    dataSource.operationQueue = operationQueue;
    dataSource.parentOperation = mapper;
    mapper.mappingOperationDataSource = dataSource;

    [mapper start];
    blockError = mapper.error;
    mappingResult = mapper.mappingResult;
];

您现在需要运行已放入我们创建的 operationQueue 的任务。正是在这个阶段建立了与现有 NSManagedObjects 的连接。

if ([operationQueue operationCount]) 
    [operationQueue waitUntilAllOperationsAreFinished];

【讨论】:

+1!我实现的关键是您的最后一步, operationQueue.waitUntilAllOperationsAreFinished()。没有它,映射过程在完成映射所需关系之前返回我的 NSManagedObject。 这对我有用。你如何让 RestKit 将对象存储回 Core Data?我试过 [[RKObjectManager sharedManager].managedObjectStore.persistentStoreManagedObjectContext saveToPersistentStore:&error];在mappingResult = mapper.mappingResult之后;但这没有帮助。有什么建议吗?【参考方案6】:

一个更面向 ios 5+ 的答案:

NSString* JSONString = jsonString;
NSString* MIMEType = @"application/json";
NSError* error = nil;
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:MIMEType];
id parsedData = [parser objectFromString:JSONString error:&error];
if (parsedData == nil && error) 
    NSLog(@"ERROR: JSON parsing error");


RKObjectMappingProvider* mappingProvider = [RKObjectManager sharedManager].mappingProvider;
RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:parsedData mappingProvider:mappingProvider];
RKObjectMappingResult* result = [mapper performMapping];
if (result) 

    NSArray *resultArray = result.asCollection;

    MyObject *object = [resultArray lastObject];
    NSLog(@"My Object: %@", object);

【讨论】:

【参考方案7】:

对于 Restkit 0.22,您可以使用此代码。这将返回一个 RKMappingResult,您可以在其中使用属性 .array 进行映射后枚举对象。

- (RKMappingResult *)mapJSONStringWithString:(NSString *)jsonString

     RKMappingResult *result = nil;

     NSError* error;
     NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
     id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:&error];
     if (parsedData == nil && error) 
        NSLog(@"json mapping error");
     

     NSDictionary *mappingsDictionary = @@"":[CustomMappingClass getMappingForUsers];

     ObjectClass *obj = [ObjectClass new];
     RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingsDictionary];
     NSError *mappingError = nil;
     mapper.targetObject = obj;
     BOOL isMapped = [mapper execute:&mappingError];
     if (isMapped && !mappingError) 
         result = [mapper mappingResult];
     

    return result;

【讨论】:

【参考方案8】:

这不是您要找的吗? http://restkit.org/api/0.10.0/Classes/RKJSONParserJSONKit.html

【讨论】:

我知道这些解析器类。他们将字符串解析为NSDictionary,这是整个反序列化过程的第一部分。我需要接受字符串并输出映射对象的功能。换句话说,在 RestKit 依次完成的三件事中(下载响应、解析下载的字符串、映射对象),我需要只做最后两件事的事情。 嘿,我也遇到了同样的问题。我有一些 JSON 需要映射到我的模型(NSManagedObject)。你找到解决办法了吗? @MikeBevz:我编写了自己的映射器。 :-) 我写了更多细节来回答这个问题。【参考方案9】:

从没有任何答案的观点来看,RestKit 中似乎还没有这个工具。我没有花更多时间试图弄清楚如何进行映射,而是使用 JsonKit 解析器的输出编写了自己的映射器,并删除了对 RestKit 的依赖(使用内置类进行网络活动)。现在我的映射器不是通用的(它对对象的布局方式和它们在 json 中的名称有一些依赖关系)但它适用于项目的目的。稍后我可能会回来并将其转换为更通用的对象映射库。

编辑:这是选择的答案,因为截至该答案的日期(2012 年 1 月 21 日)没有其他答案。从那以后,我停止了在 iOS 上的工作,并且再也没有访问过这个问题。现在我选择 Ludovic 的答案是因为另一个用户的评论和对该答案的支持。

【讨论】:

为什么选择这个答案? Ludovic 的解决方案效果很好! @Kyle:这是选择的答案,因为截至该答案的日期(2012 年 1 月 21 日)没有其他答案。从那以后,我停止了在 iOS 上的工作,并且再也没有访问过这个问题。尽管我目前无法亲自验证答案,但由于您的评论,我选择了 Ludovic 的答案。

以上是关于通过 RestKit 将 JSON 的本地 NSString 反序列化为对象(无网络下载)的主要内容,如果未能解决你的问题,请参考以下文章

RestKit 无法将 JSON 请求中格式为字符串的日期保存到我的本地数据库

如何使用带有 RKObjectMapping 的 RestKit 0.24 将本地 JSON 字符串映射到对象?

iOS RestKit 0.2 解析本地 Json 的最佳方法是啥

RestKit 0.20.1 映射本地 JSON “此类不符合键值编码...”

RestKit“无法在没有数据源的情况下执行关系映射”来自本地 JSON 文件

将 UIImage 发送到仅接受 JSON 的 Web 服务(通过 RestKit)