My question is very similar to RestKit: mapping JSON array of strings, except it is specific to RestKit 0.09. I have looked at several other questions but am still stuck.
For reasons beyond my control I cannot upgrade to RestKit 0.20. The JSON I'm working with is:
{
"brands":[
"AN",
"UO",
"FP",
"TR",
"BN"
],
"meta":{
"message":"Current Registry Configuration."
},
"event_types":[
"WEDDING",
"ANNIVERSARY",
"BIRTHDAY",
"HOUSEWARMING",
"SPECIAL OCCASION"
],
"links":[
]
}
I am able to map the "meta" (and several other domain) objects just fine. But I have not been able to map "event_types", and have no need for "brands" or "links".
My current code is as follows:
+ (void) addElementMappings:(RKObjectMapping *)mapping
{
if ([[self superclass] respondsToSelector:#selector(addElementMappings:)]) {
[[self superclass] performSelector:#selector(addElementMappings:)
withObject:mapping];
}
[mapping mapKeyPath:#"event_types"
toAttribute:#"eventType"];
RKObjectManager* objectManager = [RKObjectManager sharedManager];
if (![objectManager.mappingProvider mappingForKeyPath:#"event_types"]) {
[objectManager.mappingProvider setMapping:mapping
forKeyPath:#"event_types"]; // for KVC path
}
}
and eventType is a NSArray (I've also tried defining it as NSString - same results as below).
As is, the code throws an exception "this class is not key value coding-compliant for the key event_types."
If I change the mapKeyPath from #"event_types" to nil (similar to 0.20), I get an exception "Cannot define an element mapping an element name to map from".
If I omit the [mapping mapKeyPath:toAttribute] entirely, there are no exceptions but of course I only get the meta object, not event_types.
In RestKit 0.09, how do I map attributes without keys?
The solution was to create a class (GRConfigDTO) that contained the nested objects of interest (brands, event_types) as NSArray. The mapping was simple:
[mapping mapKeyPath:#"brands"
toAttribute:#"brands"];
[mapping mapKeyPath:#"event_types"
toAttribute:#"eventTypes"];
My predecessors had modified RestKit .09 (and our server JSON) to ensure that mapping was never ambiguous and therefore always used nil targetClass - which is why upgrading RestKit was not an option. I was working with a 3rd party service and had to deal with their JSON as-is.
The key to making it all work was specifying the targetClass in the sendObject call:
[self sendObject:nil
path:kConfig
requestType:GET
targetClass:[GRConfigDTO class]
delegate:self
callbackMethod:#selector(registryEventTypesResults:error:)];
With the targetClass set, RestKit was able to perform the mapping as expected.
Related
My app creates some ObJC objects from JSON. Things were working fine, until I added a new property to my ObjC model class, which doesn't have a counterpart in JSON.
I've configured the mapping as follows:
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
#"firstName" : #"firstname",
#"middleName" : #"middlename",
#"lastName" : #"lastname",
// etc.
#"phoneNumber" : NSNull.null // no JSON data for this one
};
}
However I get an assertion failure in Mantle, in MTLJSONAdapter initWithModelClass: "phoneNumber must either map to a JSON key path or a JSON array of key paths, got: null."
This is how I create the model object:
MyData *myData = [MTLJSONAdapter modelOfClass:[MyData class]
fromJSONDictionary:json error:&error];
How can I have the phoneNumber property in the data class without it mapping to a JSON value?
Just don't specify its mapping to + JSONKeyPathsByPropertyKey.
The accepted answer didn't end up working for me (I wasn't parsing from a JSON object) and I found subclassing the encodingBehaviorsByPropertyKey to work. The comments in the header file read as follows:
/// Determines how the +propertyKeys of the class are encoded into an archive.
/// The values of this dictionary should be boxed MTLModelEncodingBehavior
/// values.
///
/// Any keys not present in the dictionary will be excluded from the archive.
///
/// Subclasses overriding this method should combine their values with those of
/// `super`.
///
/// Returns a dictionary mapping the receiver's +propertyKeys to default encoding
/// behaviors. If a property is an object with `weak` semantics, the default
/// behavior is MTLModelEncodingBehaviorConditional; otherwise, the default is
/// MTLModelEncodingBehaviorUnconditional.
+ (NSDictionary *)encodingBehaviorsByPropertyKey;
Overriding that method from within my MTLModel subclass like so ended up working for me:
+ (NSDictionary *)encodingBehaviorsByPropertyKey {
NSMutableDictionary *encodingBehaviorsDictionary = [[super encodingBehaviorsByPropertyKey] mutableCopy];
[encodingBehaviorsDictionary removeObjectForKey:#"propertyToBeOmitted"];
return encodingBehaviorsDictionary;
}
FYI: I'm using Mantle version 2.1.
I'm stuck on why my transformable classes aren't being called. The following screenshot shows my Entity attribute as a transformable:
According to the documentation, it should automatically call the class "StringEncryptionTransformer" and perform the transformation.
I followed this guide in order to set up the class. I'm using the EncryptionTransformer and StringEncryptionTransformer classes provided, the only change I made was with the encryption to use RNcryptor.
Encryption:
return [RNEncryptor encryptData:data withSettings:kRNCryptorAES256Settings password:[self key] error:&error];
and Decryption:
return [RNDecryptor decryptData:data withPassword:[self key] error:&error];
The saved entity appears never to go through the transformation, is there something I'm missing? I tried adding an initialize to the NSManagedObject, but the results were the same.
you need to register value transformer like below
extension NSValueTransformerName {
static let classNameTransformerName = NSValueTransformerName(rawValue: "ClassNameTransformer")
}
ValueTransformer.setValueTransformer(ClassNameTransformer(), forName: .classNameTransformerName)
I am consuming JSON that is constantly evolving. I started writing code to consume the JSON using Mantle lately. It seemed like a very good choice for what I want to do. However, it seems that if the JSON being consumed has properties that do not exist in the model, the JSON transformation fails. I'm using the [MTLJSONAdapter modelOfClass:fromJSONDictionary:error:]; call to map the JSON.
Thanks in advance,
Upon closer inspection of the code, Mantle does require that all json properties map to something in the model. Otherwise, what will happen is an exception will be thrown for that property.
inside of MTLValidateAndSetValue of MTLModel, it doesn't check for the existence of the property before it sets it.
#try {
if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
if (forceUpdate || value != validatedValue) {
[obj setValue:validatedValue forKey:key];
}
return YES;
} #catch (NSException *ex) {
NSLog(#"*** Caught exception setting key \"%#\" : %#", key, ex);
// Fail fast in Debug builds.
#if DEBUG
#throw ex;
#else
if (error != NULL) {
*error = [NSError mtl_modelErrorWithException:ex];
}
return NO;
#endif
}
This is problematic if the JSON you are consuming isn't guaranteed to match your model.
I ended up doing a custom JSON representation for my work having the constructor build the object based on the incoming JSON rather than against the model. It will first iterate over the json properties and tries to map them directly to the model properties using implicit mapping. If there are any properties that require special handling, it is up to the subclass to override the init call and apply the transformation manually.
I need your help with something, as I can't get my head around this. I'm using Mantle together with CoreData in iOS.
I have relationships defined that look as follows:
Post 1:N Comment
When I pull the data from my REST Service, I create a Mantle Object Post that has a NSMutableArray of Comments in it. This works flawlessly.
I then store this in Core Data, and this is where I don't know whether I am doing things right.
[MTLManagedObjectAdapter managedObjectFromModel:post insertingIntoContext:[self getManagedObjectContext] error:&error];
So I'm doing this to store my post object into Core Data. The Core Data Model has a relationship called "post_has_comments" which is a cascading One-to-Many relationship. So on the object Post I have "posts_has_comments" -> cascading, on my object "Comment" I have a one-to-one relationship with "Nullify".
Afaik, Core Data treats this as a NSSet. What I'm trying to put in is a NSMutableArray though, as Mantle will take care of this (at least thats what a quick look in its source told me).
Unfortunately, when I get the object back from Core Data with
Post* post = [MTLManagedObjectAdapter modelOfClass:Post.class fromManagedObject:[[self fetchedResultsController] objectAtIndexPath:indexPath] error:nil];
The property comments on the post object is a empty NSSet, and I get quite some errors upon inserting the thing beforehand. The errors I get:
Core Data: annotation: repairing missing delete propagation for to-many relationship post_has_comments on object [...]
I am stuck - Maybe I am missing something huge here?
My Post Class implements the following static methods:
+ (NSDictionary *)managedObjectKeysByPropertyKey {
return #{
#"post_id" : #"id",
#"comments" : #"post_has_comments"
};
}
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"post_id" : #"id",
};
}
+ (NSDictionary *)relationshipModelClassesByPropertyKey {
return #{
#"comments" : IHComment.class
};
}
A simple workaround is to write your own property setter method and if value being set is NSSet then convert it to NSMutableArray before setting it back to your property ivar.
For example:
- (void)setComments:(NSMutableArray *)comments {
if ([comments isKindOfClass:NSSet.class]) {
_comments = [NSMutableArray arrayWithArray:[((NSSet *)comments) allObjects]];
} else {
_comments = comments;
}
}
I've done it myself quite a few times and it works like a charm!
From the Mantle documentation:
Mantle makes it easy to write a simple model layer for your Cocoa or Cocoa Touch application.
This is simply an unproven statement. Looking at the framework, I do not see where the evidence is. You should get your objects, and insert them into Core Data using Apple's APIs.
Post *cdPost = [NSEntityDescription insertNewObjectForEntityForName:#"Post"
inManagedObjectContext:self.managedObjectContext];
// configure the cdPost object with the data from the web service
for (id commentObject in commentArrayFromPostObject) {
Comment *cdComment =
[NSEntityDescription insertNewObjectForEntityForName:#"Comment"
inManagedObjectContext:self.managedObjectContext];
// configure the cdComment object with the data from the web service
cdComment.post = cdPost;
}
That's all there is to it.
Thanks to the help here and on the RestKit mailing list I've been able to parse my JSON, but now I have a new problem, parsing an empty response. To set the stage, here's what the JSON looks like when my query has results:
{"blobsList":
{"blobs":
[
{"createdOn":"2012-03-16T15:13:12.551Z","description":"Fake description ","hint":"And a useless hint","id":400,"name":"Fake CA one","publicId":"FF6","type":0},
{"createdOn":"2012-03-16T17:33:48.514Z","description":"No hint on this one, but it does have a description.","hint":"Hint","id":402,"name":"Second fake one in CA","publicId":"FF8","type":0}
]}}
So I added this to my mapping:
RKObjectMapping* blobsListMapping = [RKObjectMapping mappingForClass:[GetResponseInRegionResponseList class]];
[blobsListMapping mapKeyPath:#"blobsList" toAttribute:#"blobsList"];
[[RKObjectManager sharedManager].mappingProvider setMapping:blobMapping forKeyPath:#"blobsList.blobs"];
[[RKObjectManager sharedManager].mappingProvider setMapping:blobsListMapping forKeyPath:#"blobsList"];
And are are my Classes:
#interface GetResponseInRegionResponse : NSObject
{
NSString* name;
NSString* blobId;
NSString* description;
NSString* hint;
}
#interface GetResponseInRegionResponseList : NSObject
{
NSArray *blobsList;
}
But now the wrinkle is that my server can also return this for JSON:
{"blobsList":""}
Yeah, if the query has no results I get that back. It crashes my app
restkit.object_mapping:RKObjectMapper.m:255 Performing object mapping sourceObject: {
blobsList = "";
}
and targetObject: (null)
2012-03-22 11:56:16.233 Ferret[7399:17a07] T restkit.object_mapping:RKObjectMapper.m:269 Examining keyPath 'blobs' for mappable content...
2012-03-22 11:56:16.233 Ferret[7399:17a07] D restkit.object_mapping:RKObjectMapper.m:279 Found unmappable value at keyPath: blobs
2012-03-22 11:56:16.233 Ferret[7399:17a07] T restkit.object_mapping:RKObjectMapper.m:269 Examining keyPath 'blobsList.blobs' for mappable content...
2012-03-22 11:56:16.239 Ferret[7399:17a07] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFConstantString 0xdb1d0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key blobs.'
So I'm trying to come up with a way to get ResKit to map this empty response. I've tried all of these:
[[RKObjectManager sharedManager].mappingProvider setMapping:blobListMapping forKeyPath:#"blobsList"];
[[RKObjectManager sharedManager].mappingProvider setMapping:NULL forKeyPath:#"blobsList"];
[blobListMapping mapKeyPath:#"" toAttribute:#"blobsList"];
[blobListMapping mapKeyPath:#"blobsList" toAttribute:#"blobsList"];
But they all crash. I'm trying to make heads and tails of the source, the "this class is not key value coding-compliant for the key blobs" is puzzling since there aren't any blobs there, just blobsList. I appreciate any help, thanks!
I ran into something similar in my project. I was posting to a web service and that service was returning to me my posted object wrapped in an array. This caused RestKit to crash when parsing because it expected to only get a single object back and not a collection. To fix the problem I implemented the objectLoader:willMapData: delegate method and pulled the response object out of the array and sent that object through the RestKit parsing and all is well.
Maybe you can do something similar. Check to see if you get back a string in blobslist and convert that to an empty array before sending it through the RestKit parsing.
- (void)objectLoader:(RKObjectLoader *)objectLoader willMapData:(id *)mappableData
{
// Horrible hack to convert posted object that gets returned as an array into a single object
// that can be mapped.
//
if ([objectLoader.targetObject isKindOfClass:[TSLPostPostContract class]])
{
if ([*mappableData isKindOfClass:[NSArray class]])
{
*mappableData = [*mappableData objectAtIndex:0];
}
}
}