I'm trying to map the responses from the Sickbeard API to my objects with Mantle, but I can't figure out how, since the response is key-value based using TVDB id's as key, like this:
"data": {
"71663": {
"air_by_date": 0,
"cache": {
"banner": 1,
"poster": 1
},
"language": "en",
"network": "FOX",
"next_ep_airdate": "2014-09-28",
"paused": 0,
"quality": "Any",
"show_name": "The Simpsons",
"status": "Continuing",
"tvdbid": 71663,
"tvrage_id": 6190,
"tvrage_name": "The Simpsons"
},
"72227": {
"air_by_date": 0,
"cache": {
"banner": 1,
"poster": 1
},
"language": "en",
"network": "CBS",
"next_ep_airdate": "",
"paused": 0,
"quality": "Any",
"show_name": "Two and a Half Men",
"status": "Continuing",
"tvdbid": 72227,
"tvrage_id": 6454,
"tvrage_name": "Two and a Half Men"
}
}
Since the data object does not simply contain an array of objects like this [{"key": value},{"key": value}] but instead objects keyed by some unique id, I'm not sure how I should map it into my SBShow classes, defined like:
#import <Foundation/Foundation.h>
#import <Mantle.h>
#interface SBShow : MTLModel <MTLJSONSerializing>
#property (nonatomic, strong) NSNumber *tvdbid;
#property (nonatomic, strong) NSString *showName;
#property (nonatomic, strong) NSString *network;
#property (nonatomic, strong) NSString *status;
#end
#implementation SBShow
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{#"showName": #"show_name"};
}
+ (NSValueTransformer *)dateJSONTransformer {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy-MM-dd"];
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
return [formatter dateFromString:str];
} reverseBlock:^(NSDate *date) {
return [formatter stringFromDate:date];
}];
}
#end
Any help would be greatly appreciated.
You can do it like this, by adding the key to the rest of the 'JSON dictionary':
NSMutableArray *shows = [NSMutableArray array];
// data is an NSDictionary, representing the 'data' key in the JSON
[data enumerateKeysAndObjectsUsingBlock:^(NSString *tvdbID, NSDictionary *showData, BOOL *stop) {
NSMutableDictionary *modelDictionary = [showData mutableCopy];
modelDictionary[#"tvdbid"] = tvdbID;
NSError *error = nil;
SBShow *show = [MTLJSONAdapter modelOfClass:SBShow.class
fromJSONDictionary:modelDictionary
error:&error];
[shows addObject:show];
}];
NSLog(#"Show models are %#", shows);
You can write your own transformer to encapsulate this logic and apply it to the data key if appropriate.
Related
I have NSDictionary object as result of NSJSONSerialization, and it has child that is array of dictionary called list. Look at my json result:
{
"tags": [
"rofl",
"lmao",
"funny",
"haha",
"lmfao"
],
"result_type": "exact",
"list": [
{
"defid": 3689813,
"word": "Lol",
"author": "Lol B",
"permalink": "http://lol.urbanup.com/3689813",
"definition": "The name 'Lol' is an abreviated form of the name '[Laurence]'.",
"example": "\"Hey Lol, you alright?\"\r\n\r\n\"..Well i was chattin to Lol and he said..\"",
"thumbs_up": 44617,
"thumbs_down": 9926,
"current_vote": ""
},
------- bla bla bla ----------
],
"sounds": [
"http://media.urbandictionary.com/sound/lol-871.mp3",
------- bla bla bla ----------
]
}
I also created a class model for item (child of list).
#import <Foundation/Foundation.h>
#interface Item : NSObject
#property (strong, nonatomic) NSString *defid;
#property (strong, nonatomic) NSString *word;
#property (strong, nonatomic) NSString *author;
#property (strong, nonatomic) NSURL *permalink;
#property (strong, nonatomic) NSString *definition;
#property (strong, nonatomic) NSString *example;
-(instancetype)initWithDictionary:(NSDictionary *)dict;
#end
I also created its initializer
-(instancetype)initWithDictionary:(NSDictionary *)dict {
if (self = [super init]) {
self.defid = [dict valueForKey:#"defid"];
self.word = [dict valueForKey:#"word"];
self.author = [dict valueForKey:#"author"];
self.permalink = [dict valueForKey:#"permalink"];
self.definition = [dict valueForKey:#"definition"];
self.example = [dict valueForKey:#"example"];
}
return self;
}
I already create an array of list item (NSDictionary), my problem is I want to map those into my class so I can create an instance of list item. So how to convert item in array into my item class?
Should I iterate each item of array and call that initialization?
or any elegant method for doing that?
Additional question
I am relatively new into iOS development. I just want to parse my request result. Is there any tips for fetch some request and assign its result for datasource a table view (array)?
This is my fetch method:
- (void)fetchDataFromMashape:(NSURL *)URL {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"GET"];
[request setValue:API_KEY_MASHAPE forHTTPHeaderField:API_MASHAPE_HEADER_1];
[request setValue:API_ACCEPT forHTTPHeaderField:API_MASHAPE_HEADER_2];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Result: %#", jsonResult);
_results = [[NSArray alloc] initWithArray:[jsonResult valueForKey:#"list"]];
}];
[task resume];
}
My advice is to create a static method with the json list as a parameter
+ (NSArray<Item*>*) parseJson:(NSArray*) json {
NSMutableArray<Item*>* results = [[NSMutableArray alloc] initWithCapacity:son.length];
for (NSDictionary* anItem in json){
[results append:[[Item alloc] initWithDictionary:anItem]];
}
return results;
}
Data from the server
{
"type": "reward",
"stamp_pos": "1",
"reward_id": "350",
"reward_title": "dfddffd",
"reward_cost": "1",
"reward_cost_text": "Stamp Reward",
"reward_tnc": "12pm between Monday - Thursday.",
"member_reward_tracker_id": "1180",
"redeem_now": "1",
"redeemed": "0",
"active": "1",
"redeemed_date": "0000-00-00 00:00:00",
"reward_featured_image": "rs/img/someimage"
},
What I am trying to do is
My Model.h
typedef NS_ENUM(NSInteger, CardType) {
CardTypeStampAvailable = 1,
CardTypeStampRedeemed,
CardTypeRewardAvailable,
CardTypeRewardRedeemNow,
CardTypeRewardRedeemed,
};
#interface ModelCardReward : MTLModel <MTLJSONSerializing>
#property(nonatomic) CardType myCardType;
#property(nonatomic, strong) NSString *rewardTitle
#property(nonatomic, strong) NSString *rewardCostText;
#end
My Model.m
#implementation ModelCard
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"rewardTitle" : #"reward_title",
#"rewardCostText" : #"reward_cost",
};
}
I want to have the variable myCardType to have the NS_ENUM value
based on the dictionary parameters.
That is
if ([json[#"type"] isEqualToString:reward] && [json[#"redeem_now"] isEqualToString:#"1"]) {
myCardType = CardTypeRewardAvailable;
}
Any help would be great.
You can achieve this in Mantle 2 with a custom NSValueTransformer and by mapping your property to multiple JSON fields. The example below should be clear.
ModelCardReward.h
typedef NS_ENUM(NSInteger, CardType) {
CardTypeStampAvailable = 1,
CardTypeStampRedeemed,
CardTypeRewardAvailable,
CardTypeRewardRedeemNow,
CardTypeRewardRedeemed,
};
#interface ModelCardReward : MTLModel <MTLJSONSerializing>
#property(nonatomic) CardType myCardType;
#property(nonatomic, strong) NSString *rewardTitle;
#property(nonatomic, strong) NSString *rewardCostText;
#end
ModelCardReward.m
#import "ModelCardReward.h"
#implementation ModelCardReward
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"myCardType" : #[ #"type", #"redeem_now" ],
#"rewardTitle" : #"reward_title",
#"rewardCostText" : #"reward_cost",
};
}
+ (NSValueTransformer *)myCardTypeJSONTransformer {
return [MTLValueTransformer transformerUsingForwardBlock:^NSNumber *(NSDictionary *value, BOOL *success, NSError *__autoreleasing *error) {
if ([value[#"type"] isEqualToString:#"reward"] && [value[#"redeem_now"] isEqualToString:#"1"]) {
return #(CardTypeRewardAvailable);
}
//logic for other enum values
//return some default value
return #(CardTypeRewardRedeemed);
}];
}
#end
Calling code:
NSDictionary *dict = #{
#"type": #"reward",
#"stamp_pos": #"1",
#"reward_id": #"350",
#"reward_title": #"dfddffd",
#"reward_cost": #"1",
#"reward_cost_text": #"Stamp Reward",
#"reward_tnc": #"12pm between Monday - Thursday.",
#"member_reward_tracker_id": #"1180",
#"redeem_now": #"1",
#"redeemed": #"0",
#"active": #"1",
#"redeemed_date": #"0000-00-00 00:00:00",
#"reward_featured_image": #"rs/img/someimage"
};
NSError *error = nil;
ModelCardReward *model = [MTLJSONAdapter modelOfClass:ModelCardReward.class fromJSONDictionary:dict error:&error];
if (error) {
NSLog(#"Error creating model from JSON is %#", error);
} else {
NSLog(#"Model is %#", model);
}
I found that my CouchModel (TSCAssetUploadDO) saves the attachment but does not save the property.
TSCAssetUploadDO *assetDO = [[TSCAssetUploadDO alloc] initWithNewDocumentInDatabase: _localAssetUploadDatabase];
NSData *data = UIImageJPEGRepresentation(contact.avatar, 1.0); // may need to resize.
NSString *attachmentName = [docID stringByAppendingString:#".jpg"];
[assetDO createAttachmentWithName:attachmentName type:#"image/jpeg" body:data];
assetDO.relatedDocID = docID;
assetDO.docType = #"contact";
RESTOperation *op2 = [assetDO save];
//[op2 wait]; originally thought that making it sync may work
[op2 onCompletion:^{
if (op2.error) NSLog(#"ERROR [TOUCHDB] %#", op2.error);
else
{
NSLog(#"YES! it saved");
}
if (completionBlock)
{
completionBlock(op2.error, contact);
}
//[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationDataUpdated object:nil];
}];
This is resulting to a document like this ( with saved attachment, no property like docType and relatedDocID though )
{
"_id": "D50ED630-34ED-4A02-A9C8-204E79A0648B",
"_rev": "1-b0ce9eaa1fb2f86dc9ae619e27ffe1ea",
"_attachments": {
"QSPNGC665.jpg": {
"content_type": "image/jpeg",
"revpos": 1,
"digest": "md5-u9V0rgoSRN5cUW2T3xh0hw==",
"length": 117500,
"stub": true
}
}
}
Below is the CouchModel that I just used.
#interface TSCAssetUploadDO: CouchModel
#property(nonatomic,retain) NSString *relatedDocID;
#property(nonatomic,retain) NSString *docType;//entity or contact
#property bool *toProcess;
#property (retain) NSDate* created_at;
#end
#implementation TSCAssetUploadDO
- (NSDictionary*) propertiesToSave {
// Initialize created_at the first time the document is saved:
if (self.created_at == nil)
self.created_at = [NSDate date];
return [super propertiesToSave];
}
#end
Is there anything that I did wrong?
Solved my issue. need to add #dynamic
#implementation TSCAssetUploadDO
#dynamic relatedDocID, docType, toProcess, created_at;
<... rest of the code >
#end
I am having trouble with RESTKIT and mapping the following JSON via RESTKit .20.1. I have a nested JSON array and I am trying to map via the RelationshipMapper. I am able to map the 5 chart_data object successfully but the related nested chart_datatum objects do not map to the ChartDataDetails class. All of the properties are blank.
I would like the following object structure.
Alert Instance
Array of ChartData objects
Each ChartData object should contain 1 ChartDataDetails object <---- this object is blank
Please see the link at the bottom for the screenshot of the XCode Debugger for the current state.
Can someone please provide some assistance?
Thanks,
G
{
"alert_instance": {
"alert_template_id": 1,
"alert_time_period": "2013-05-29",
"chart_data": [
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "provider_id",
"datatype": 1,
"chart_data_value": "1"
}
},
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "Provider_name",
"datatype": 2,
"chart_data_value": "Stubbs, Drew"
}
},
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "Posting_date",
"datatype": 3,
"chart_data_value": "05/12/2013"
}
},
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "Charges",
"datatype": 5,
"chart_data_value": "229"
}
},
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "Payments",
"datatype": 5,
"chart_data_value": "-1023.11"
}
}
]
}
Model classes
AlertInstance.h
#interface AlertInstance : NSObject
#property (nonatomic, copy) NSNumber* alertTemplateId;
#property (nonatomic, copy) NSDate* alertTimePeriod;
#property (nonatomic, retain) NSMutableArray* chartData;
#end
ChartData.h
#interface ChartData : NSObject
#property (nonatomic, retain) ChartDataDetails *chartDataDetails;
#end
ChartDataDetails.h
#interface ChartDataDetails : NSObject
#property (nonatomic, copy) NSNumber* chartDataId;
#property (nonatomic, copy) NSString* chartDataName;
#property (nonatomic, copy) NSNumber* chartDataType;
#property (nonatomic, copy) NSNumber* chartDataValue;
#end
code for mappings..
RKObjectMapping* chartDataDetailMapping = [RKObjectMapping mappingForClass:[ChartDataDetails class]];
[chartDataDetailMapping addAttributeMappingsFromDictionary:#{
#"chart_data_id": #"chartDataId",
#"chart_data_name": #"chartDataName",
#"datatype": #"chartDataType",
#"chart_data_value": #"chartDataValue",
}];
RKObjectMapping* chartDataMapping = [RKObjectMapping mappingForClass:[ChartData class]];
[chartDataMapping addAttributeMappingsFromDictionary:#{
#"chart_data":#"chartData"}];
RKObjectMapping* alertInstanceMapping = [RKObjectMapping mappingForClass:[AlertInstance class]];
[alertInstanceMapping addAttributeMappingsFromDictionary:#{
#"alert_template_id": #"alertTemplateId",
#"alert_time_period": #"alertTimePeriod",
}];
[alertInstanceMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"chart_data"
toKeyPath:#"chartData"
withMapping:chartDataMapping]];
[alertInstanceMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"chart_datum"
toKeyPath:#"chartDataDetails"
withMapping:chartDataDetailMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:alertInstanceMapping
pathPattern:nil
keyPath:#"alert_instance"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURL *URL = [NSURL URLWithString:#"http://ideainnovationsmobiledev1.cloudapp.net:3000/alert_instances/1.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ responseDescriptor ]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
link to screen shot of the debugger
https://docs.google.com/file/d/0Bwaj6O1rXiKaeFIwT2s4Rm1Gb00/edit?usp=sharing
EDIT:
Ok I figured it out.
I needed to chain the ChartDetailsMapping to the ChartData as follows.
[chartDataMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"chart_datum"
toKeyPath:#"chartDataDetails"
withMapping:chartDataDetailMapping]];
[alertInstanceMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"chart_data"
toKeyPath:#"chartData"
withMapping:chartDataMapping]];
Here is my setup, Post a list of objects in Json to server and got back with updated results that will be mapped to core data and updated.
-- Post Boday --
{
"memberId": "1000000",
"contacts": [
{
"phoneNumber": "+12233333333",
"firstName": "john",
"lastName": "H"
},
{
"phoneNumber": "+12244444444",
"firstName": "mary",
"lastName": "K"
}
]
}
-- Post Response --
{
"contacts": [
{
"phoneNumber": "+12233333333",
"firstName": "john",
"lastName": "k",
"isMember": "yes"
},
{
"phoneNumber": "+12244444444",
"firstName": "mary",
"lastName": "k",
"isMember": "no"
}
]
}
I found another thread which discusses very similar to my case. RestKit: How does one post an array of objects?
This is my setup.
-- SHContact.h --
#interface SHContact : NSManagedObject {}
#property (nonatomic, strong) NSString* phoneNumber;
#property (nonatomic, strong) NSString* firstName;
#property (nonatomic, strong) NSString* lastName;
#property (nonatomic, strong) NSNumber* isMember;
#end
-- SHContactPost.m --
#import "SHContactPost.h"
#implementation SHContactPost
#synthesize contacts;
#synthesize memberId;
- (NSArray*)contacts {
return <list-of-SHContact>;
}
- (NSString *)memberId {
return #"my-member-id";
}
#end
-- RK Mapping --
// Setup our object mappings for SHContact
RKManagedObjectMapping* contactMapping =
[RKManagedObjectMapping mappingForClass:[SHContact class]
inManagedObjectStore:objectManager.objectStore];
contactMapping.primaryKeyAttribute = #"phoneNumber";
[contactMapping mapAttributes:#"phoneNumber",
#"firstName", #"lastName", #"isMember", nil];
[objectManager.mappingProvider setObjectMapping:contactMapping
forKeyPath:#"contacts"];
[[objectManager mappingProvider]
setSerializationMapping:[contactMapping inverseMapping]
forClass:[SHContact class]];
RKObjectMapping *cPostMapping = [RKObjectMapping
mappingForClass:[SHContactPost class]];
[cPostMapping mapKeyPath:#"contacts"
toRelationship:#"contacts"
withMapping:contactMapping];
[cPostMapping mapAttributes:#"memberId", nil];
[objectManager.mappingProvider
setSerializationMapping:[cPostMapping inverseMapping]
forClass:[SHContactPost class]];
[objectManager.router routeClass:[SHContactPost class]
toResourcePath:#"/contacts"
forMethod:RKRequestMethodPOST];
objectManager.serializationMIMEType = RKMIMETypeJSON;
-- Post to server --
SHContactPost *post = [[SHContactPost alloc] init];
RKObjectManager* manager = [RKObjectManager sharedManager];
[manager postObject:post delegate:self];
Server post and response is SUCCESSFUL. However, Reskit was not able to map the result back to the list of SHContact objects. This is the exception. Am I missing something?
restkit.network:RKObjectLoader.m:222 Encountered errors during mapping:
Cannot map a collection of objects onto a non-mutable collection.
Unexpected destination object type 'SHContactPost'
UPDATE: This is what I changed to make it work for me.
-- RK Mapping --
// Setup our object mappings for SHContact
RKManagedObjectMapping* contactMapping =
[RKManagedObjectMapping mappingForClass:[SHContact class]
inManagedObjectStore:objectManager.objectStore];
contactMapping.primaryKeyAttribute = #"phoneNumber";
[contactMapping mapAttributes:#"phoneNumber",
#"firstName", #"lastName", #"isMember", nil];
[objectManager.mappingProvider addObjectMapping:contactMapping];
-- Post to server --
SHContactPost *post = [[SHContactPost alloc] init];
RKObjectManager* manager = [RKObjectManager sharedManager];
RKObjectMapping* responseMapping = [manager.mappingProvider
objectMappingForClass:[SHContact class]];
[manager postObject:post usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.targetObject = nil;
responseMapping.rootKeyPath = #"contacts";
loader.objectMapping = responseMapping;
}];
loader.targetObject needs to set it to nil, otherwise, it won't work.