Handling ignored properties in Mantle - ios

I need to have one property which is not present in JSON but it need to be set when I try to use this Model Object. I think it'd be easier to show you the example...
Here is my sample JSON:
[{
"name" : "Safari",
"key" : "safari",
"app_url_scheme" : "http://www.twitter.com",
"actions" :
[{
"key" : "show_profile",
"url_format" : "http://www.twitter.com/{{profile_screenname}}"
}]
}
]
And JSONKeyPathsByPropertyKey method looks like this:
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
#"appName" : #"name",
#"appKey" : #"key",
#"appURLScheme" : #"app_url_scheme",
#"appActions" : #"actions",
#"isInstalled" : NSNull.null
};
}
So, as you can see there is this isInstalled method which should be ignored during mapping (as there isn't field like this in JSON) but I need to set this property as soon as this Object is fully mapped. What is more I need to set it based on other property provided in JSON. How to achieve this?
I've found solution like this:
#"videoType" : #"#Selector(videoTypeFromString:, type)",
//! implemented on instance you are parsing
- (NSUInteger)videoTypeFromString:(NSString *)type
{
if ([type isEqualToString:#"shortVideo"]) {
return VideoTypeShort;
}
return VideoTypeLong;
}
But this #selector is never being called...

Related

Mantle property class based on another property?

How can I use Github Mantle to choose a property class based on another property in the same class? (or in the worse case another part of the JSON object).
For example if i have an object like this:
{
"content": {"mention_text": "some text"},
"created_at": 1411750819000,
"id": 600,
"type": "mention"
}
I want to make a transformer like this:
+(NSValueTransformer *)contentJSONTransformer {
return [MTLValueTransformer transformerWithBlock:^id(NSDictionary* contentDict) {
return [MTLJSONAdapter modelOfClass:ETMentionActivityContent.class fromJSONDictionary:contentDict error:nil];
}];
}
But the dictionary passed to the transformer only includes the 'content' piece of the JSON, so I don't have access to the 'type' field. Is there anyway to access the rest of the object? Or somehow base the model class of 'content' on the 'type'?
I have previously been forced to do hack solutions like this:
+(NSValueTransformer *)contentJSONTransformer {
return [MTLValueTransformer transformerWithBlock:^id(NSDictionary* contentDict) {
if (contentDict[#"mention_text"]) {
return [MTLJSONAdapter modelOfClass:ETMentionActivityContent.class fromJSONDictionary:contentDict error:nil];
} else {
return [MTLJSONAdapter modelOfClass:ETActivityContent.class fromJSONDictionary:contentDict error:nil];
}
}];
}
You can pass the type information by modifying the JSONKeyPathsByPropertyKey method:
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
NSStringFromSelector(#selector(content)) : #[ #"type", #"content" ],
};
}
Then in contentJSONTransformer, you can access the "type" property:
+ (NSValueTransformer *)contentJSONTransformer
{
return [MTLValueTransformer ...
...
NSString *type = value[#"type"];
id content = value[#"content"];
];
}
I've had a similar issue, and I suspect my solution isn't much better than yours.
I have a common base class for my Mantle objects, and after each is constructed, I call a configure method to give them chance to set up properties that are dependent on more than one "base" (== JSON) property.
Like this:
+(id)entityWithDictionary:(NSDictionary*)dictionary {
NSError* error = nil;
Class derivedClass = [self classWithDictionary:dictionary];
NSAssert(derivedClass,#"entityWithDictionary failed to make derivedClass");
HVEntity* entity = [MTLJSONAdapter modelOfClass:derivedClass fromJSONDictionary:dictionary error:&error];
NSAssert(entity,#"entityWithDictionary failed to make object");
entity.raw = dictionary;
[entity configureWithDictionary:dictionary]; // Give the new entity a chance to set up derived properties
return entity;
}

Restkit Saving Minor Changes to Objects after Mapping [duplicate]

So let's say I have a JSON object like such:
{
"userList" : [
{
"ID" : 1,
"firstName" : "John",
"lastName" : "Doe"
},
{
"ID" : 2,
"firstName" : "Jane",
"lastName" : "Doe"
}
]
}
I am able to map this object into my user class which have the following attribute:
ID,
firstName,
lastName,
createdDate,
modifiedData
The Problem arise when I am need to update modified date I want to be able to insert a data-time stamp whenever I do a mapping along with when I modified the data while in offline mode.
So my question is, how do I map JSON object to Core Data while also inserting some data that is not present in the JSON object. Is this even possible?
================
My Mapping Function, if it helps:
+ (RKObjectMapping *)mapping {
// Create a singleton instance of the mapping object.
__strong static RKEntityMapping *_mapping = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RKManagedObjectStore *store = [[RKObjectManager sharedManager] managedObjectStore];
_mapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([self class]) inManagedObjectStore:store];
// Map attributes with the same name in the JSON and the model.
[_mapping addAttributeMappingsFromDictionary:#{#"ID": #"ID",
#"firstName" : #"firstName",
#"lastName" : #"lastName"}];
// Set primaryKeyAttribute
_mapping.identificationAttributes = #[#"ID"];
});
return _mapping;
}
Handle this outside RestKit, but in a way that is triggered by RestKit (and any other change):
Override willSave on your managed object subclass and update the modified date whenever it's called (setting the primitive value to avoid recursion).

NSDictionary that doesn't change

I want to make myself things easier so I'm creating a dictionary that is used by other function (to reach key by object or object by key) but that dictionary is always static. Is this fine way to do it or I need property or something else?
+ (NSDictionary *)dictionaryWithCategoriesAndStrings
{
return #{
kNewsCategoryAll : #(NewsCategoryAll),
kNewsCategoryRadio : #(NewsCategoryRadio),
kNewsCategoryEconomics : #(NewsCategoryEconomics),
kNewsCategoryCulture : #(NewsCategoryCulture),
kNewsCategorySport : #(NewsCategorySport),
kNewsCategoryTravel : #(NewsCategoryTravel),
kNewsCategoryMusic : #(NewsCategoryMusic),
kNewsCategorySociety : #(NewsCategorySociety),
kNewsCategoryHealth : #(NewsCategoryHealth)
};
}
So now I always access this same dictionary through function [self dictionaryWithCategoriesAndString];
Note: Those are keys are static strings declared at top and objects are NSNumbers with integer.
Rather than exposing the static to the entire class, you could create it within the method, and initialise it only once with gcd:
This is thread safe.
+ (NSDictionary *)dictionaryWithCategoriesAndStrings {
static NSDictionary *dict;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dict = #{
kNewsCategoryAll : #(NewsCategoryAll),
kNewsCategoryRadio : #(NewsCategoryRadio),
kNewsCategoryEconomics : #(NewsCategoryEconomics),
kNewsCategoryCulture : #(NewsCategoryCulture),
kNewsCategorySport : #(NewsCategorySport),
kNewsCategoryTravel : #(NewsCategoryTravel),
kNewsCategoryMusic : #(NewsCategoryMusic),
kNewsCategorySociety : #(NewsCategorySociety),
kNewsCategoryHealth : #(NewsCategoryHealth)
};
});
return dict;
}
You will be creating a new NSDictionary every time you call this method, so you won't really be accessing the same dictionary as it will just be a new identical one each time. You won't be able to make a property either if you're using it statically. Maybe something a little more like this just so you are accessing the same dictionary each time.
To access this in an instance method, you could use [[self class] dictionaryWithCategoriesAndStrings].
static NSDictionary* dict;
+ (NSDictionary *)dictionaryWithCategoriesAndStrings
{
if(dict == nil)
{
dict = #{
kNewsCategoryAll : #(NewsCategoryAll),
kNewsCategoryRadio : #(NewsCategoryRadio),
kNewsCategoryEconomics : #(NewsCategoryEconomics),
kNewsCategoryCulture : #(NewsCategoryCulture),
kNewsCategorySport : #(NewsCategorySport),
kNewsCategoryTravel : #(NewsCategoryTravel),
kNewsCategoryMusic : #(NewsCategoryMusic),
kNewsCategorySociety : #(NewsCategorySociety),
kNewsCategoryHealth : #(NewsCategoryHealth)
};
}
return dict;
}
Almost right. Every time that selector is executed, a new NSDictionary is created. That is bad. It should only be created once, and it should only be created lazily.
#property (strong, nonatomic) NSDictionary *categoryDict;
- (NSDictionary *) categoryDict
{
if( !_categoryDict)
{
_categoryDict = #{ #"Key" :#"value" , ....};
}
return _categoryDict;
}
Now the dictionary is only created once. and you can get to the dictionary using dot notation.

Validating each property before returning object

While using Mantle, is there a possibility to before returning the object we are creating (on this case via JSON) to verify that X and Y properties are not nil?
Imagine this class:
#interface Person : MTLModel <MTLJSONSerializing>
#property(nonatomic,strong,readonly)NSString *name;
#property(nonatomic,strong,readonly)NSString *age;
#end
I want a way to verify that if the JSON I received doesn't have the name (for some reason there was an issue on server's DB) I will return a nil Person, as it doesn't make sense to create that object without that property set.
Whilst you could override the initialiser. It seems more concise to override the validate: as this is called at the last stage before Mantle returns the deserialised object. Makes sense to put all your validation logic in a validate method...
See the final line of the MTLJSONAdapter
id model = [self.modelClass modelWithDictionary:dictionaryValue error:error];
return [model validate:error] ? model : nil;
This tells us that if our custom model returns NO from validate, then Mantle will discard the object.
So you could in your subclass simply perform the following:
- (BOOL)validate:(NSError **)error {
return [super validate:error] && self.name.length > 0;
}
Ideally in your own implementation you probably want to return an appropriate error.
The validate method will then call Foundation's validateValue:forKey:error: for every property that you registered with Mantle in JSONKeyPathsByPropertyKey. So if you want a more controlled validation setup you could also validate your data here..
You can use the MTLJSONSerializing protocol method classForParsingJSONDictionary: to return nil rather than an invalid object:
// In your MTLModelSubclass.m
//
+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary {
if (JSONDictionary[#"name"] == nil || JSONDictionary[#"age"] == nil) {
return nil;
}
return self.class;
}
In fact I don't use Mantle, but for validation I use another GitHub Library called RPJSONValidator
It tells you the type that you expect and what type the value has been arrived.
A simple example code
NSError *error;
[RPJSONValidator validateValuesFrom:json
withRequirements:#{
#"phoneNumber" : [RPValidatorPredicate.isString lengthIsGreaterThanOrEqualTo:#7],
#"name" : RPValidatorPredicate.isString,
#"age" : RPValidatorPredicate.isNumber.isOptional,
#"weight" : RPValidatorPredicate.isString,
#"ssn" : RPValidatorPredicate.isNull,
#"height" : RPValidatorPredicate.isString,
#"children" : RPValidatorPredicate.isArray,
#"parents" : [RPValidatorPredicate.isArray lengthIsGreaterThan:#1]
} error:&error];
if(error) {
NSLog(#"%#", [RPJSONValidator prettyStringGivenRPJSONValidatorError:error]);
} else {
NSLog(#"Woohoo, no errors!");
}
Each key-value pair describes requirements for each JSON value. For example, the key-value pair #"name" : RPValidatorPredicate.isString will place a requirement on the JSON value with key "name" to be an NSString. We can also chain requirements. For example, #"age" : RPValidatorPredicate.isNumber.isOptional will place a requirement on the value of "age" to be an NSNumber, but only if it exists in the JSON.
I use a very old version of Mantle. YMMV
You can override the [MTLModel modelWithExternalRepresentation] selector. Make sure to call [super modelWithExternalRepresentation] and then add your own code to check for validate data.
I followed a small issue opened on Mantle:
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error
{
BOOL isValid = NO;
if (self = [super initWithDictionary:dictionaryValue error:error])
{
isValid = ...
}
return isValid?self:nil;
}
So in the end just override:
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error

Restkit request dynamic mapping

I'm using RestKit for posting objects to the server. In my object I have two properties: name and socialId.
I want to send to server only properties that have data.
If name != nil send:
{
"name" : "name",
}
If socialId != 0 send:
{
"socialId" : socialId,
}
But RestKit sends all data. For example:
{
"name" : "",
"socialId" : 0,
}
How can I change this behavior?
The answer is to using [RKDynamicMapping setObjectMappingForRepresentationBlock:]
You should check representation properties in block and add attributes mapping for desired properties.

Resources