Désérialisation de NSString locale de JSON en objets via RestKit (pas de téléchargement réseau)
Question
Est-il possible de désérialiser un NSString
de JSON en objets via RestKit? J'ai vérifié la liste des API ici et je n'ai pas trouvé quelque chose qui servirait à cet effet. Les plus proches que j'ai pu trouver sont les différentes classes d'analyseurs qui retournent NSDictionary
après l'analyse de l'entrée. Je suppose que RestKit utilise ces analyseurs après avoir téléchargé la réponse, donc je pense que la fonctionnalité est disponible quelque part dans RestKit mais pas exposée publiquement.
Si rien ne me manque et que cette fonctionnalité n'est pas exposée, quelles seraient les alternatives? Deux évidents ne semblent pas très prometteurs: obtenez le NSDictionary
résultant et essayez de me désérialiser (réimplémentant efficacement RestKit) ou essayez de plonger dans la source de RestKit et voir si cela peut être exposé (semble fastidieux et sujet aux erreurs).
Merci d'avance pour toute aide.
PS: L'idée est qu'une propriété de chaîne sur un objet désérialisé est en fait la représentation JSON d'un autre ensemble d'objets (JSON incorporé dans un sens) et elle est désérialisée à la demande pendant l'exécution.
La solution
Assez "simple":
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]);
Autres conseils
À partir de 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);
}
Cela fonctionne pour 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]);
}
Cela fonctionne pour Restkit 0.20, en utilisant des Entités de données de base .Il est basé sur la solution donnée par @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]
}
Vous pouvez voir comment RestKit fait cela en interne dans la classe RKManagedObjectResponseMapperOperation
.
Cette opération se déroule en trois étapes.
La première consiste à analyser la chaîne JSON en NSDictionary, NSArrays, etc. C'est la partie la plus simple.
id parsedData = [RKMIMETypeSerialization objectFromData:data
MIMEType:RKMIMETypeJSON
error:error];
Ensuite, vous devez exécuter une opération de mappage pour convertir ces données en vos NSManagedObjects.C'est un peu plus compliqué.
__block NSError *blockError = nil;
__block RKMappingResult *mappingResult = nil;
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 1;
[[RKObjectManager sharedManager].managedObjectStore.persistentStoreManagedObjectContext performBlockAndWait:^{
N'oubliez pas de remplacer ce dictionnaire par vos propres mappages.La clé [NSNull null]
mappe cet objet à partir de la racine.
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;
}];
Vous devez maintenant exécuter les tâches qui ont été placées dans la file d'opération que nous avons créée.C'est à ce stade que les connexions aux NSManagedObjects existants sont établies.
if ([operationQueue operationCount]) {
[operationQueue waitUntilAllOperationsAreFinished];
}
Une réponse plus orientée 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);
}
Pour Restkit 0.22, vous pouvez utiliser ce code.Cela renvoie un RKMappingResult dans lequel vous pouvez énumérer les objets après le mappage en utilisant la propriété .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;
}
N'est-ce pas ce que vous recherchez? http://restkit.org/api/0.10.0/Classes/RKJSONParserJSONKit.html
À en juger par les vues sans réponse, il semble que cette fonctionnalité n'existe pas encore dans RestKit.Au lieu de passer plus de temps à essayer de comprendre comment faire le mappage, j'ai écrit mon propre mappeur en utilisant la sortie de l'analyseur JsonKit et j'ai supprimé la dépendance à RestKit (utilisé les classes intégrées pour l'activité réseau).À l'heure actuelle, mon mappeur n'est pas générique (il a quelques dépendances sur la façon dont les objets sont disposés et leurs noms en json) mais il fonctionne pour les besoins du projet.Je pourrais revenir plus tard et en faire une bibliothèque de mappage d'objets plus générique plus tard.
MODIFIER: Cette réponse a été sélectionnée car il n'y avait pas d'autre réponse à la date de cette réponse (21 janvier 2012).Depuis, j'ai arrêté de travailler sur iOS et je n'ai plus jamais revu cette question.Maintenant, je sélectionne la réponse de Ludovic en raison du commentaire d'un autre utilisateur et des votes positifs pour cette réponse.