Mapping iTunes Search API results with RESTKit - ios

I'm trying to load iTunes Search API results into custom objects using RestKit. I've defined a simple NSObject subclass for the results:
#interface LSiTunesResult : NSObject
#property (nonatomic) NSInteger itemID;
#property (nonatomic) NSInteger artistID;
#property (strong, nonatomic) NSString *title;
#property (strong, nonatomic) NSString *artistName;
#property (strong, nonatomic) NSString *collectionName;
#property (strong, nonatomic) NSString *itemDescription;
#property (strong, nonatomic) NSURL *imageURL;
#property (strong, nonatomic) NSURL *thumbnailURL;
#property (strong, nonatomic) NSURL *trackPreviewURL;
#property (strong, nonatomic) NSURL *trackViewURL;
//
+ (RKObjectMapping *)objectMapping;
#end
For simplicity's sake, I've defined object mapping
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[LSiTunesResult class]];
[mapping addAttributeMappingsFromDictionary:#{
#"artistName" : #"artistName"
}];
I've tried a few different ways to actually request the results, using both a custom RKObjectManager and an RKObjectRequestOperation. My simple example with RKObjectRequestOperation looks like:
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[LSiTunesResult objectMapping] pathPattern:nil keyPath:#"results" statusCodes:nil];
RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:urlRequest responseDescriptors:#[responseDescriptor]];
[operation.HTTPRequestOperation setAcceptableContentTypes:[NSSet setWithObject:#"text/javascript"]];
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"The search results are: %#", [mappingResult array]);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure reason: %#", [error localizedDescription]);
}];
[operation start];
Unfortunately, I get the following error from RKResponseMapperOperation: "restkit.network:RKResponseMapperOperation.m:197 Failed to parse response data: Loaded an unprocessable response (200) with content type 'text/javascript'"
I have RestKit network logging on, and it appears that the response from the iTunes API marks the content type as 'text/javascript', while the body contains valid JSON. I've tried telling the RKObjectRequestOperation that text/javascript is an acceptable content type.
Does anyone have any experience with the iTunes API and RestKit? I'm curious if this is a simple mapping issue on my end, or if the iTunes API claiming the content type is javascript while it appears to be valid JSON is throwing off RestKit. Below is a sample of the iTunes Search API response that is logged during my example.
2012-10-21 01:48:54.061 Listen[99898:617] T restkit.network:RKHTTPRequestOperation.m:124 GET 'http://itunes.apple.com/search?term=The%20Lumineers&country=US&media=music&limit=1' (200):
response.headers={
"Cache-Control" = "no-transform, max-age=60";
Connection = "keep-alive";
"Content-Encoding" = gzip;
"Content-Length" = 519;
"Content-Type" = "text/javascript; charset=utf-8";
Date = "Sun, 21 Oct 2012 05:48:54 GMT";
Vary = "Accept-Encoding";
"X-Apple-Partner" = "origin.0";
"apple-timing-app" = "49 ms";
"x-apple-application-instance" = 1018018;
"x-apple-application-site" = NWK;
"x-apple-orig-url-path" = "/search?term=The%20Lumineers&country=US&media=music&limit=1";
"x-apple-translated-wo-url" = "/WebObjects/MZStoreServices.woa/ws/wsSearch?term=The%20Lumineers&country=US&media=music&limit=1";
"x-webobjects-loadaverage" = 0;
}
response.body=
{
"resultCount":1,
"results": [
{"wrapperType":"track", "kind":"music-video", "artistId":350720227, "trackId":516035614, "artistName":"The Lumineers", "trackName":"Ho Hey", "trackCensoredName":"Ho Hey", "artistViewUrl":"https://itunes.apple.com/us/artist/the-lumineers/id350720227?uo=4", "trackViewUrl":"https://itunes.apple.com/us/music-video/ho-hey/id516035614?uo=4", "previewUrl":"http://a404.v.phobos.apple.com/us/r30/Video/fd/81/e1/mzi.gxodlwda..640x256.h264lc.u.p.m4v", "artworkUrl30":"http://a1838.phobos.apple.com/us/r30/Video/v4/41/64/37/416437c2-c02e-4706-3f13-a47f50ec2ccc/Cover.40x30-75.jpg", "artworkUrl60":"http://a621.phobos.apple.com/us/r30/Video/v4/41/64/37/416437c2-c02e-4706-3f13-a47f50ec2ccc/Cover.80x60-75.jpg", "artworkUrl100":"http://a1629.phobos.apple.com/us/r30/Video/v4/41/64/37/416437c2-c02e-4706-3f13-a47f50ec2ccc/Cover.100x100-75.jpg", "collectionPrice":1.99, "trackPrice":1.99, "releaseDate":"2012-04-03T07:00:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"notExplicit", "trackTimeMillis":160952.0, "country":"USA", "currency":"USD", "primaryGenreName":"Singer/Songwriter"}]
}

Two things to make this work.
The first one you got:
operation.HTTPRequestOperation.acceptableContentTypes = [NSSet setWithObject:#"text/javascript"];
The second one is to register the json serializer for this type.
[RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:#"text/javascript"];
Then it all works!

Related

Restkit - Relationship mapping not working

I'm experiencing Restkit, using the version 0.25.0. I followed exactly what the documentation says for the relationship mapping, but for some reasons, I have an empty mapping result !
But when I remove the relationship object (data), I have a mapping result !!! But of course, the mapping result doesn't contain the data I need, the one I added as relationship.
I followed the example they have in this link :
https://github.com/RestKit/RestKit/wiki/Object-mapping#relationships
When I print the JSON with the debugger, here's the output :
{
"status": "success",
"data": {
"id": 11,
"provider": "email",
"uid": "riri#gmail.com",
"name": null,
"nickname": null,
"image": null,
"email": "riri#gmail.com",
"country": "United States",
"city": "Milan, Metropolitan City of Milan, Italy",
"gender": "m",
"birthday": "2015-06-25"
}
}
Here's the code how I make the request :
RKObjectManager *manager = [RKObjectManager sharedManager];
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
RKObjectMapping *jsonResponseMapping = [JSONResponse mappingObject];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:jsonResponseMapping
method:RKRequestMethodAny
pathPattern:#"/auth/sign_in"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptor:responseDescriptor];
NSDictionary *parameters = #{
#"email": user.email,
#"password": user.password
};
[manager postObject:user path:#"/auth/sign_in" parameters:parameters success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSDictionary *headerFields = operation.HTTPRequestOperation.response.allHeaderFields;
[self updateUserInfoForResponse:[mappingResult firstObject] headerFields:headerFields];
if (successBlock) {
successBlock([mappingResult firstObject]);
}
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
if (failureBlock) {
failureBlock(error.userInfo[RKObjectMapperErrorObjectsKey][0], error);
}
}];
.m of JSONResponse class:
+ (RKObjectMapping *)mappingObject {
RKObjectMapping *jsonResponseMapping = [RKObjectMapping mappingForClass:[JSONResponse class]];
[jsonResponseMapping addAttributeMappingsFromDictionary:#{
#"status": #"status",
#"errors": #"errors",
}];
RKRelationshipMapping *userRelationShip = [RKRelationshipMapping relationshipMappingFromKeyPath:#"data" toKeyPath:#"data" withMapping:[User mappingObject]];
[jsonResponseMapping addPropertyMapping:userRelationShip];
return jsonResponseMapping;
}
.h Of JSONResponse class :
#import <RestKit/Restkit.h>
#import <Foundation/Foundation.h>
#import "User.h"
#interface JSONResponse : NSObject
#property (nonatomic, copy) NSString *status;
#property (nonatomic) User *data;
#property (nonatomic, copy) NSDictionary *errors;
/**
#function mappingObject
#return RKObjectMapping mapping for the json response
*/
+ (RKObjectMapping *)mappingObject;
#end
.m of User class
#import "User.h"
#implementation User
+ (RKObjectMapping *)mappingObject {
RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[User class]];
[userMapping addAttributeMappingsFromDictionary:#{
#"email": #"email",
#"password": #"password",
#"gender": #"gender",
#"birthday": #"dateOfBirth",
#"city": #"city",
#"country": #"country"
}];
return userMapping;
}
#end
.h of User class
#class RKObjectMapping;
#interface User : NSObject
#property (nonatomic, copy) NSString *email;
#property (nonatomic, copy) NSString *password;
#property (nonatomic, copy) NSString *gender;
#property (nonatomic, copy) NSString *dateOfBirth;
#property (nonatomic, copy) NSString *city;
#property (nonatomic, copy) NSString *country;
/**
#function mappingObject
#return RKObjectMapping mapping object for user
*/
+ (RKObjectMapping *)mappingObject;
#end
Is there anything wrong in my models objects ? The relationship is set properly right ? I do have the data property in the JSONResponse, and the JSON contains correctly the keyPath data.
So I'm pretty confused why I have results when I remove my relationship, and why the mapping result is empty when I have the relationship. It's even doesn't go in the failureCallback, the operation is successful, the result is empty.
Any ideas ??
EDIT :
Here's the logs :
2015-09-15 07:27:50.649 testRestkit[53698:3448725] D restkit.object_mapping:RKPropertyInspector.m:154 Cached property inspection for Class 'JSONResponse': {
data = "<RKPropertyInspectorPropertyInfo: 0x7facf2c5fa00>";
status = "<RKPropertyInspectorPropertyInfo: 0x7facf2c5f7d0>";
}
2015-09-15 07:27:50.650 testRestkit[53698:3448725] D restkit.object_mapping:RKPropertyInspector.m:154 Cached property inspection for Class 'User': {
email = "<RKPropertyInspectorPropertyInfo: 0x7facf2c60680>";
}
2015-09-15 07:27:50.820 testRestkit[53698:3448725] I restkit.network:RKObjectRequestOperation.m:150 POST 'http://sandrotchikovani.com/test.php'
2015-09-15 07:27:51.236 testRestkit[53698:3448938] D restkit.object_mapping:RKMapperOperation.m:407 Executing mapping operation for representation: {
data = {
email = "lol#gmail.com";
};
status = success;
}
and targetObject: <User: 0x7facf2c05820>
2015-09-15 07:27:51.237 testRestkit[53698:3448938] T restkit.object_mapping:RKMapperOperation.m:350 Examining keyPath '<null>' for mappable content...
2015-09-15 07:27:51.237 testRestkit[53698:3448938] D restkit.object_mapping:RKMapperOperation.m:330 Found mappable data at keyPath '<null>': {
data = {
email = "lol#gmail.com";
};
status = success;
}
2015-09-15 07:27:51.237 testRestkit[53698:3448938] D restkit.object_mapping:RKMapperOperation.m:433 Finished performing object mapping. Results: {
}
Isn't weird that in the log, it says and targetObject: <User: 0x7facf2c05820> ? When I remove the relationship, there is a mapping result and the targetObject, displays "null".
You are calling postObject:..., and when you do that RestKit will map back to the original object. In this case that's a user and that's why you're seeing the log and targetObject: <User: 0x7facf2c05820>.
The easiest thing for you to do is to setup your JSONResponse so that you post it and receive the response into it. It already has the required user so a simple change to the request descriptor to pull out the user fields should be enough.
Alternatively there are a bunch of other questions about mapping to a different object after posting.

RKMappingResult has objects, but they NSLog as null

What I'm doing
Using RestKit, I'm making a GET request to get a JSON object that contains an array of User objects that populate a UITableView. I pass that array into a private NSArray called users which becomes _users (I'm still fuzzy on this). This works, and the table populates fine. I can access the individual objects in the _users array from my other methods, such as [UITableViewCell cellForRowAtIndex].
However, at the same time I pull the data down, and before I call [self.tableView reloadData] from inside the success block of [RKObjectManager getObjectsAtPath...], I want to process the individual objects a little bit.
My problem
Using [RKObjectManager getObjectsAtPath parameters success failure], success returns the RKMappingResult as expected, and I pass its array to a _users, which populates my UITableView. This works, but in the same success block, I try NSLog'ing _users[i] and it returns *nil description*. I know the values are being set at some point, because I populate my UITableViewCells by calling _users[i] in another method.
Hopefully more helpful info
When I NSLog(#"%#", _users) from inside the success block, and know for a fact there are 3 objects in the array, I see:
(
(null),
(null),
(null)
).
I can provide more info, I'm just not sure what to put. I can also show my code, but it's basically out of the book from the RestKit docs.
User object
#interface User : NSObject
#property (nonatomic, strong) NSString *id;
#property (nonatomic, strong) NSString *email;
#property (nonatomic, strong) NSString *username;
#property (nonatomic, strong) NSString *fullName;
#property (nonatomic, strong) NSString *bio;
#property (nonatomic) NSDate *dob;
#property (nonatomic, strong) NSString *avatar;
#property (nonatomic, strong) NSString *avatarMeta;
#property (nonatomic, strong) NSString *location;
#property (nonatomic, strong) NSURL *url;
#property (nonatomic, strong) NSString *enabled;
#end
RKObjectManager example
*note - some pieces have been removed for security reasons
- (void)loadUsers {
NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithDictionary:#{#"token": self.token}];
NSString *path = #"/api/v1/g/feed";
if ( self.cursor ) {
path = [NSString stringWithFormat:#"%#/%#", path, self.cursor];
}
[[RKObjectManager sharedManager] getObjectsAtPath:path
parameters:params
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
_users = mappingResult.array;
NSLog(#"Last User: %#",_users[0]); // *nil description*
NSLog(#"Array: %#", _users); // ((null),(null),(null),(null),(null),(null),(null),(null),(null),(null))
[self.tableView reloadData]; // table gets populated correctly
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No feed available: %#", error);
}];
}
Most likely you're overriding the description method, probably by adding your own property with that name, in the user class and setting that to an empty string. This breaks everything. You should rename that property to overview or something like that and update your usage.

RestKit 0.20 Post Array Json issues

This is the json that I need to post to services.
{
"deviceToken":"asdfasdfasdf",
"alarm": [
{
"start" "8:30",
"end": "9:30",
"line": "156",
"code": "xxxafsdfasdf",
"station": "asdfa",
"stationLeft": 5,
"available": true,
"times": 2
}]
}
The response data just have one more field "id" in alarm:
{
"deviceToken":"asdfasdfasdf",
"alarm": [
{
"id":1,
"start" "8:30",
"end": "9:30",
"line": "156",
"code": "xxxafsdfasdf",
"station": "asdfa",
"stationLeft": 5,
"available": true,
"times": 2
}]
}
Then I define two objects:
DeviceAlarm Object:
#interface DeviceAlarm : NSObject
#property(nonatomic, strong) NSMutableArray *alarm;
#property(nonatomic, copy) NSString *deviceToken;
#end
Alarm Object:
#interface Alarm : NSObject
#property(nonatomic, copy) NSNumber *id;
#property(nonatomic, copy) NSString *start;
#property(nonatomic, copy) NSString *end;
#property(nonatomic, copy) NSString *code;
#property(nonatomic, copy) NSString *line;
#property(nonatomic, copy) NSString *station;
#property(nonatomic, copy) NSNumber *stationLeft;
#property(nonatomic) BOOL available;
#property(nonatomic, copy) NSNumber *times;
#end
This is my code to post.
DeviceAlarm* devicealarm = [[DeviceAlarm alloc] init];
Alarm* alarm = [[Alarm alloc] init];
alarm.start = #"8:00";
alarm.end = #"9:30";
alarm.line = #"156";
alarm.code = #"fasdfasdf";
alarm.station = #"asdfas";
alarm.stationLeft = #1000;
alarm.available = true;
alarm.times = #1;
devicealarm.alarm = [NSArray arrayWithObjects:alarm , nil];
devicealarm.deviceToken = #"adsfasdfasdf";
RKObjectMapping *alarmMapping = [RKObjectMapping requestMapping];
[alarmMapping addAttributeMappingsFromArray:#[#"start",#"end",#"code",#"station", #"stationLeft",#"available",#"times",#"line"]];
RKObjectMapping *deviceMapping = [RKObjectMapping requestMapping];
RKRelationshipMapping *alarmRelationship = [RKRelationshipMapping
relationshipMappingFromKeyPath:#"alarm"
toKeyPath:#"alarm"
withMapping:alarmMapping];
[deviceMapping addAttributeMappingsFromArray:#[#"deviceToken"]];
[deviceMapping addPropertyMapping:alarmRelationship];
NSString* path = #"/api/alarm/asdfasdf";
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:deviceMapping
objectClass:[DeviceAlarm class]
rootKeyPath:nil];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[DeviceAlarm DeviceAlarmResponseMapping]
pathPattern:nil
keyPath: nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://127.0.0.1:5000/"]];
[manager addRequestDescriptor:requestDescriptor];
[manager addResponseDescriptor:responseDescriptor];
[manager postObject:devicealarm path:path parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *result) {
NSLog(#"Loading mapping result: %#", result);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
Then I check the post data in services, I found restkit post an error array json.
alarm dict lost.
{
"deviceToken":"asdfasdfasdf",
"alarm": [
"8:30",
"9:30",
"156",
"xxxafsdfasdf",
"asdfa",
5,
true,
2
]
}
Please help me~ :)
LOL, I have fixed my issues, I changed the field type from "NSMutableArray" to "NSSet" in DeviceAlarm Object, and then it works. I don't know why. Hope it can help us.

RestKit mapping XML to Core Data

I working with it for two days... I want get remote xml then parse/map it to core data. I have read bunch of tutorials, so at this moment I can connect with local server (yyuupii!), get xml, but I have problem with mapping. I get
-[RKObjectLoader canParseMIMEType:] Unable to find parser for MIME Type 'application/xml'
-[RKObjectLoader isResponseMappable] Encountered unexpected response with status code: 200 (MIME Type: application/xml -> URL:
http:///list.xml -- http:/// --
http:/// -- http:///) 2012-10-24
14:13:12.201 Sierpien[4650:907] Error
Domain=org.restkit.RestKit.ErrorDomain Code=4 "The operation couldn’t
be completed. (org.restkit.RestKit.ErrorDomain error 4.)"
Could you gave me some advice I will be thankful.
My XML
<packs>
<pack>
<cover>cover.png</cover>
<info>Jakis.adres.pl</info>
<link>Opis</link>
<name>wrzesień</name>
<price>5.00</price>
</pack>
<pack>
<cover>cover2.png</cover>
<info>Jakis1.adres.pl</info>
<link>Opis31</link>
<name>wrzesień12</name>
<price>15.00</price>
</pack>
</packs>
My Entity
#interface Pack : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * link;
#property (nonatomic, retain) NSString * price;
#property (nonatomic, retain) NSString * info;
#property (nonatomic, retain) NSString * cover;
#end
My Implementation
- (id)initClient
{
self = [super init];
if (self) {
RKObjectManager *client = [RKObjectManager objectManagerWithBaseURL:[RKURL URLWithString:#"http://10.1.1.5:8888/"]];
NSLog(#"I am your RKObjectManager singleton : %#", [RKObjectManager sharedManager]);
client.serializationMIMEType = RKMIMETypeXML;
RKObjectMapping* listMapping = [RKObjectMapping mappingForClass:[Pack class]];
[listMapping mapKeyPath:#"cover" toAttribute:#"cover"];
[listMapping mapKeyPath:#"name" toAttribute:#"name"];
[listMapping mapKeyPath:#"info" toAttribute:#"info"];
[listMapping mapKeyPath:#"link" toAttribute:#"link"];
[listMapping mapKeyPath:#"price" toAttribute:#"price"];
[[RKObjectManager sharedManager].mappingProvider setMapping:listMapping forKeyPath:#"packs.pack"];
}
return self;
}
- (void)loadPacks {
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/list.xml" delegate:self];
}
Ok I throw away RKObjectManager, now I connect with RKClient, get xml with
[[RKClient sharedClient] get:#"/list.xml" delegate:self];
Then I parse Xml by GDataXML, fill Pack with that parsed data. It works.

Parsing JSON using RestKit

I have been using RestKit for sometime but some APIs have changed in the latest version and I'm no longer able to parse simple JSON.
Here's the payload I have:
{
"result":true,
"items":[
{
"id":"1",
"receiver":"11011101"
},
{
"id":"2",
"receiver":"11011101"
}
]
}
How can I parse the contents of the "items" dictionary as instances of the object Conversation I have created?
Using the code below doesn't work (objects are never mapped):
RKObjectMapping* conversationMapping = [RKObjectMapping mappingForClass:[Conversation class]];
[conversationMapping mapKeyPath:#"id" toAttribute:#"id"];
[conversationMapping mapKeyPath:#"receiver" toAttribute:#"receiver"];
[[RKObjectManager sharedManager].mappingProvider setMapping:conversationMapping forKeyPath:#"items"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/getConversations" delegate:self];
Conversation class
#interface Conversation : NSObject {
NSString *id;
NSString *receiver; }
+ (void)objectMapping;
#property (nonatomic, strong) NSString *id; #property (nonatomic, strong) NSString *receiver;
#end
#implementation Conversation
#synthesize id;
#synthesize receiver;
+ (void)objectMapping {
RKObjectMapping* conversationMapping = [RKObjectMapping mappingForClass:[Conversation class]];
[conversationMapping mapKeyPath:#"id" toAttribute:#"id"];
[conversationMapping mapKeyPath:#"receiver" toAttribute:#"receiver"];
[[RKObjectManager sharedManager].mappingProvider setMapping:conversationMapping forKeyPath:#"items"];
}
#end
How is your root object defined (the one that holds "result" and your Conversation "items")? That should look something like this:
#interface MyResponse
#property (nonatomic, strong) NSArray* items;
#property (nonatomic, assign) BOOL result;
with the appropriate mapping for that as well.
I solved the problem. It was something totally not related to RestKit. The content type of the response coming back from the server was not set to JSON, after fixing that object mapping worked fine.

Resources