Mantle property class based on another property? - ios

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;
}

Related

Handling ignored properties in Mantle

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...

Custom NSManagedObject subclass constructor

I have a question about CoreData and NSManagedObject.
In the architecture of my application I have a REST client That builds instances of objects That I receive from the server.
For example I have this method:
- (NSArray*) getAllCards:(NSDictionary*) jsonResponse;
That takes the JSON response and returns an array of Cards.
It's ok ...
Now I need to save my Card in CoreData.
I defined my model so I have some subclass of NSManagedObject.
For example: Card: NSManagedObject
but I would like to have a simple constructor to construct the object from the JSON and then pass this to my manager who will add it in CoreData.
I would not pass some info related to CoreData (eg. Context ..) to my Client REST ... I would like to have the logic in my CoreData CoreData Manager ..
How can I define a valid constructor of my Cards?
Can I define a constructor in this way?
- (id) initWithName:(NSString*) name
cardId:(NSString*) cardId
type:(NSString*) type
{
self = [super initWithEntity:nil insertIntoManagedObjectContext:nil]; //This is wrong..
if (self) {
self.name = name;
self.cardId = cardId;
self.type = type;
}
}
I see no need to override the init methods. Just give your CoreDataManager class a method that takes the dictionary and generates the core data entities.
-(void)storeCardsFromJSON:(NSDictionary*)dictionary {
for (NSDictionary *cardInfo in dictionary[#"cardArray"]) {
Card *newCard = [NSEntityDescription insert...];
newCard.cardID = cardInfo[#"cardID"];
// etc.
}
// save
}
Optionally, return an array of newly created cards. In this method you could also filter out the cards that already exist (e.g. based on the cardID attribute) and modify those instead of inserting them again.
In your app, you should only use the Card objects from Core Data. There is no need to have a different class of objects, or use NSDictionary objects.
Maybe it will help: for all of my NSManagedObject subclasses, that can be created from JSON, I added a category with names like MyObject+JSON and I have 2 methods there:
+ (instancetype)deserializeFromDictionary:(NSDictionary *)dictionary inContext:(NSManagedObjectContext *)context;
- (void)updateFromDictionary:(NSDictionary *)dictionary;
I use inContext: part because I usually insert objects in batches using private context for the whole transaction.
Methods look like this:
+ (instancetype)deserializeFromDictionary:(NSDictionary *)dictionary inContext:(NSManagedObjectContext *)context;
{
if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]] || !context) {
return nil;
}
MyObject *anObject = (MyObject *)[NSEntityDescription insertNewObjectForEntityForName:#"MyObject" inManagedObjectContext:context];
[anObject updateFromDictionary:dictionary];
return anObject;
}
And
- (void)updateFromDictionary:(NSDictionary *)dictionary
{
NSParameterAssert(dictionary);
if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]]) {
return;
}
self.identifier = dictionary[#"id"];
self.title = dictionary[#"title"]; //and so on
}

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

Replace getter for all attributes in CoreData Entity

In my CoreData model I have an Entity Article with 20 properties, and a child entity Variant, with a to-one relationship "master" to Article. So Variant has all attributes of Article parent class, but I want that if an attribute is nil, the getter should return the one of the master Article.
In code:
- (NSString *)getSomeAttribute {
NSString *tmp = self.someAttribute;
if (tmp == nil)
tmp = self.master.someAttribute;
return tmp;
}
I don't want to write 20 getters like this, is there a way to write a "general" getter for all attributes at once?
You can't skip the getters, because if you don't implement them, Core Data will generate them. What you can do is factor all of the custom code into one core method and have your other getters call that. You still end up with a bunch of getters, but they're one-liners. Something like this (warning, typed into a web browser and not compiled):
- (id)valueForKeyFromSelfOrMaster:(NSString *)key
{
[self willAccessValueForKey:key];
id value = [self primitiveValueForKey:key];
[self didAccessValueForKey:key];
if (value == nil) {
value = [self.master valueForKey:key];
}
return value;
}
- (NSString *)someAttribute
{
return [self valueForKeyFromSelfOrMaster:#"someAttribute"];
}

Resources