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
Related
In my MTLModel subclass I have this:
#property (assign, nonatomic) NSInteger catId;
And of course this in the implementation:
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"catId" : #"cat_id"
};
}
But what if my server friends decide to change cat_id to a string in the JSON response? How can I handle this case, and convert it to an int so that I don't get Mantle errors?
We also used Mantle for quite some time, but at the end migrated to handwritten parser/serializers, as the task itself seem to be trivial.
Though, we also have this kind of problems: what if server return something we do not expect (e.g. NSDictionary instead of NSString).
To prevent our app from crashing we use this simple tool: Fuzzer.
Basically the tool provides a method that takes a sample and a block. The block evaluates several times, each time bringing slightly mutated version of the sample. You can check behaviour of the models/parsers/serializers using the mutants, to ensure that your code handles unexpected data gracefully.
Here is example taken from project's README:
- (void)test {
NSDictionary *sample = #{
#“name” : #“John Doe”,
#“age” : #42
};
UserDeserializer *deserializer = [UserDeserializer new];
FZRRunner *runner = [FZRRunner runnerWithBuiltinMutationsForSample:sample];
NSArray *reports = [runner enumerateMutantsUsingBlock:^(NSDictionary *mutant) {
[deserializer deserializeUser:mutant];
}];
XCTAssertEqual(reports.count, 0);
}
if([obj isKindOfClass:NSClassFromString(#"__NSCFNumber")])
{
//if it is int or number
}
else
{
}
May be above method will help you
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;
}
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
}
I'm using RestKit (the latest version 0.23.1), and I have defined a custom transformer between a string value to a number field as follows:
mapping.valueTransformer = [RKBlockValueTransformer valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class inputValueClass, __unsafe_unretained Class outputValueClass) {
return [inputValueClass isSubclassOfClass:[NSString class]];
} transformationBlock:^BOOL(id inputValue, __autoreleasing id *outputValue, __unsafe_unretained Class outputClass, NSError *__autoreleasing *error) {
if (![inputValue isKindOfClass:[NSString class]]) {
return NO;
}
// conversion logic follows...
return YES;
}];
The transformer works properly, however the validation block is not being called (I've set a breakpoint to verify that), which forces me to check in the transformationBlock that the inputValue is indeed an instance of NSString.
Why is the validation block not being invoked, what am I doing wrong?
I couldn't find any issue on this in RestKit GitHub page.
You are setting the transformer directly onto the mapping and as the only transformer so it is assumed that it always applies.
You want to set the transformer to a RKCompoundValueTransformer (Using defaultValueTransformer And your custom transformer) as it does perform validation before applying transformations.
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"];
}