Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Throw an error if mappings don't match property keys #294

Merged
merged 7 commits into from

3 participants

@robb
Owner

Fixes #293

  • JSON
  • Core Data
@robb
Owner

The same should probably be done for managed object mappings.

@robb robb changed the title from Throw an error if JSON mappings to match property keys to [WIP] Throw an error if mappings to match property keys
@robb
Owner

@jspahrsummers Since this was silently ignored before, putting this straight into master could theoretically break some builds downstream. Let me know if I should rather target 2.0-development

@jspahrsummers

Let's do this on master, since the previous behavior was basically silent failure.

Mantle/MTLJSONAdapter.h
@@ -63,6 +63,9 @@
// The provided JSONDictionary is not valid.
extern const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary;
+// The model's JSON mapping is incompatible with its property keys.
@jspahrsummers Owner

Can you be more specific with this (and the error message)? Maybe something like:

The model's implementation of +JSONKeyPathsByPropertyKey included a key which does not actually exist in +propertyKeys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@robb robb changed the title from [WIP] Throw an error if mappings to match property keys to Throw an error if mappings to match property keys
@robb
Owner

Not sure if CI is down but this is ready for review

@jspahrsummers jspahrsummers self-assigned this
@jspahrsummers

This looks good, but I worry about the performance impact of +setWithArray: and -isSubsetOfSet:, since these are in such a critical and frequent code path.

Maybe we should instead enumerate the array, and check containment within the set one-by-one?

@robb
Owner

Maybe we should instead enumerate the array, and check containment within the set one-by-one?

Sounds reasonable

@robb robb changed the title from Throw an error if mappings to match property keys to Throw an error if mappings don't match property keys
@robb
Owner

:sa:

Mantle/MTLJSONAdapter.m
@@ -102,7 +103,24 @@ - (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)mo
NSMutableDictionary *dictionaryValue = [[NSMutableDictionary alloc] initWithCapacity:JSONDictionary.count];
- for (NSString *propertyKey in [self.modelClass propertyKeys]) {
+ NSSet *propertyKeys = [self.modelClass propertyKeys];
+
+ for (NSString *JSONKeyPath in self.JSONKeyPathsByPropertyKey.allKeys) {
@jspahrsummers Owner

The allKeys is expensive and redundant here. Dictionaries are already fast enumerated by key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Mantle/MTLManagedObjectAdapter.m
@@ -250,6 +251,23 @@ - (id)modelFromManagedObject:(NSManagedObject *)managedObject processedObjects:(
}
+ (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject error:(NSError **)error {
+ NSSet *propertyKeys = [modelClass propertyKeys];
+
+ for (NSString *mappedPropertyKey in [modelClass managedObjectKeysByPropertyKey].allKeys) {
@jspahrsummers Owner

As above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@robb
Owner

:fries:

@robb robb referenced this pull request
Merged

Longer lived JSON adapters #278

@jspahrsummers jspahrsummers merged commit 9f5f0aa into master
@jspahrsummers jspahrsummers deleted the mismatched-keypaths branch
@robb robb referenced this pull request in octokit/octokit.objc
Open

[WIP] Mantle 2.0 #184

@yaltar yaltar commented on the diff
Mantle/MTLJSONAdapter.m
@@ -102,7 +103,24 @@ - (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)mo
NSMutableDictionary *dictionaryValue = [[NSMutableDictionary alloc] initWithCapacity:JSONDictionary.count];
- for (NSString *propertyKey in [self.modelClass propertyKeys]) {
+ NSSet *propertyKeys = [self.modelClass propertyKeys];
+
+ for (NSString *JSONKeyPath in self.JSONKeyPathsByPropertyKey) {
+ if ([propertyKeys containsObject:JSONKeyPath]) continue;
+
+ if (error != NULL) {
@yaltar
yaltar added a note

Hi,
Just want to add my 2 cents on this pull request which was merged.

IMO It shouldn't throw error in case of umapped JSON Key. I've multiple case where server respond lot of field in JSON response and i don't want to get them all in my object, so no reason to throw an error.

At least it should be configurable, one should be able to choose to be "strict" or not on mapping.

More than that, the pull requested merged seems to have error in it, as it silently fails.

Seems due to line 150 in "antle/MTLJSONAdapter.h" where code is
if (error != NULL) {

Should be
if (error == NULL) {

As there is no current error, it show nothing.

Thanks in advance for fixing the issue.

Regards,

@robb Owner
robb added a note

This only affects the property mapping in your model, so if you say map the property foo to the JSON key bar, this will only make sure that your class actually has a property foo. If your server's response actually contains bar or a different set of keys entirely is a different matter.

The if (error != NULL) is needed to make sure that we're not dereferencing a NULL pointer when assigning the NSError later.

Also check out #325 where we're changing this to an exception.

@yaltar
yaltar added a note
@robb Owner
robb added a note

Not sure about the explanation on property mapping. While doing debug step by step because i didn't have anymore my json mapped to object i reach the case where server response have a field in JSON "bar", which i didn't use in my Model on my app, and the debug goes to line 150.

Odd, do you have a test case you could put into a gist?

The error variable is an argument to the method of type NSError **. If the user passes in NULL, we wouldn't know where to store the reference to the error object we're creating. Again, this will change anyway.

@yaltar
yaltar added a note
@yaltar
yaltar added a note
@yaltar
yaltar added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 31, 2014
  1. @robb
Commits on Apr 17, 2014
  1. @robb

    Update documentation

    robb authored
  2. @robb

    *sigh*

    robb authored
  3. @robb

    WS

    robb authored
  4. @robb
Commits on Apr 23, 2014
  1. @robb
Commits on Apr 24, 2014
  1. @robb

    Remove unnecessary allKeys

    robb authored
This page is out of date. Refresh to see the latest.
View
4 Mantle/MTLJSONAdapter.h
@@ -63,6 +63,10 @@ extern const NSInteger MTLJSONAdapterErrorNoClassFound;
// The provided JSONDictionary is not valid.
extern const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary;
+// The model's implementation of +JSONKeyPathsByPropertyKey included a key which
+// does not actually exist in +propertyKeys.
+extern const NSInteger MTLJSONAdapterErrorInvalidJSONMapping;
+
// Converts a MTLModel object to and from a JSON dictionary.
@interface MTLJSONAdapter : NSObject
View
20 Mantle/MTLJSONAdapter.m
@@ -13,6 +13,7 @@
NSString * const MTLJSONAdapterErrorDomain = @"MTLJSONAdapterErrorDomain";
const NSInteger MTLJSONAdapterErrorNoClassFound = 2;
const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary = 3;
+const NSInteger MTLJSONAdapterErrorInvalidJSONMapping = 4;
// An exception was thrown and caught.
const NSInteger MTLJSONAdapterErrorExceptionThrown = 1;
@@ -102,7 +103,24 @@ - (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)mo
NSMutableDictionary *dictionaryValue = [[NSMutableDictionary alloc] initWithCapacity:JSONDictionary.count];
- for (NSString *propertyKey in [self.modelClass propertyKeys]) {
+ NSSet *propertyKeys = [self.modelClass propertyKeys];
+
+ for (NSString *JSONKeyPath in self.JSONKeyPathsByPropertyKey) {
+ if ([propertyKeys containsObject:JSONKeyPath]) continue;
+
+ if (error != NULL) {
@yaltar
yaltar added a note

Hi,
Just want to add my 2 cents on this pull request which was merged.

IMO It shouldn't throw error in case of umapped JSON Key. I've multiple case where server respond lot of field in JSON response and i don't want to get them all in my object, so no reason to throw an error.

At least it should be configurable, one should be able to choose to be "strict" or not on mapping.

More than that, the pull requested merged seems to have error in it, as it silently fails.

Seems due to line 150 in "antle/MTLJSONAdapter.h" where code is
if (error != NULL) {

Should be
if (error == NULL) {

As there is no current error, it show nothing.

Thanks in advance for fixing the issue.

Regards,

@robb Owner
robb added a note

This only affects the property mapping in your model, so if you say map the property foo to the JSON key bar, this will only make sure that your class actually has a property foo. If your server's response actually contains bar or a different set of keys entirely is a different matter.

The if (error != NULL) is needed to make sure that we're not dereferencing a NULL pointer when assigning the NSError later.

Also check out #325 where we're changing this to an exception.

@yaltar
yaltar added a note
@robb Owner
robb added a note

Not sure about the explanation on property mapping. While doing debug step by step because i didn't have anymore my json mapped to object i reach the case where server response have a field in JSON "bar", which i didn't use in my Model on my app, and the debug goes to line 150.

Odd, do you have a test case you could put into a gist?

The error variable is an argument to the method of type NSError **. If the user passes in NULL, we wouldn't know where to store the reference to the error object we're creating. Again, this will change anyway.

@yaltar
yaltar added a note
@yaltar
yaltar added a note
@yaltar
yaltar added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid JSON mapping", nil),
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"%1$@ could not be parsed because its JSON mapping contains illegal property keys.", nil), modelClass]
+ };
+
+ *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorInvalidJSONMapping userInfo:userInfo];
+ }
+
+ return nil;
+ }
+
+ for (NSString *propertyKey in propertyKeys) {
NSString *JSONKeyPath = [self JSONKeyPathForPropertyKey:propertyKey];
if (JSONKeyPath == nil) continue;
View
4 Mantle/MTLManagedObjectAdapter.h
@@ -150,6 +150,10 @@ extern const NSInteger MTLManagedObjectAdapterErrorUniqueFetchRequestFailed;
// NSArray or NSSet of MTLModel<MTLManagedObjectSerializing> instances.
extern const NSInteger MTLManagedObjectAdapterErrorUnsupportedRelationshipClass;
+// The model's implementation of +managedObjectKeysByPropertyKey included a key
+// which does not actually exist in +propertyKeys.
+extern const NSInteger MTLManagedObjectAdapterErrorInvalidManagedObjectMapping;
+
// Converts a MTLModel object to and from an NSManagedObject.
@interface MTLManagedObjectAdapter : NSObject
View
18 Mantle/MTLManagedObjectAdapter.m
@@ -19,6 +19,7 @@
const NSInteger MTLManagedObjectAdapterErrorUnsupportedManagedObjectPropertyType = 5;
const NSInteger MTLManagedObjectAdapterErrorUnsupportedRelationshipClass = 6;
const NSInteger MTLManagedObjectAdapterErrorUniqueFetchRequestFailed = 7;
+const NSInteger MTLManagedObjectAdapterErrorInvalidManagedObjectMapping = 8;
// Performs the given block in the context's queue, if it has one.
static id performInContext(NSManagedObjectContext *context, id (^block)(void)) {
@@ -250,6 +251,23 @@ - (id)modelFromManagedObject:(NSManagedObject *)managedObject processedObjects:(
}
+ (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject error:(NSError **)error {
+ NSSet *propertyKeys = [modelClass propertyKeys];
+
+ for (NSString *mappedPropertyKey in [modelClass managedObjectKeysByPropertyKey]) {
+ if ([propertyKeys containsObject:mappedPropertyKey]) continue;
+
+ if (error != NULL) {
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid entity attribute mapping", nil),
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"%1$@ could not be parsed because its entity attribute mapping contains illegal property keys.", nil), modelClass]
+ };
+
+ *error = [NSError errorWithDomain:MTLManagedObjectAdapterErrorDomain code:MTLManagedObjectAdapterErrorInvalidManagedObjectMapping userInfo:userInfo];
+ }
+
+ return nil;
+ }
+
CFMutableDictionaryRef processedObjects = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (processedObjects == NULL) return nil;
View
3  MantleTests/MTLCoreDataTestModels.h
@@ -54,3 +54,6 @@
@end
+// Maps a non-existant property "name" to the "string" attribute.
+@interface MTLIllegalManagedObjectMappingModel : MTLModel <MTLManagedObjectSerializing>
+@end
View
14 MantleTests/MTLCoreDataTestModels.m
@@ -105,3 +105,17 @@ + (NSString *)managedObjectEntityName {
}
@end
+
+@implementation MTLIllegalManagedObjectMappingModel
+
++ (NSString *)managedObjectEntityName {
+ return @"Parent";
+}
+
++ (NSDictionary *)managedObjectKeysByPropertyKey {
+ return @{
+ @"name": @"username"
+ };
+}
+
+@end
View
13 MantleTests/MTLJSONAdapterSpec.m
@@ -88,6 +88,19 @@
expect(error.code).to.equal(MTLJSONAdapterErrorInvalidJSONDictionary);
});
+it(@"should return nil and error with an illegal JSON mapping", ^{
+ NSDictionary *values = @{
+ @"username": @"foo"
+ };
+
+ NSError *error = nil;
+ MTLIllegalJSONMappingModel *model = [MTLJSONAdapter modelOfClass:MTLIllegalJSONMappingModel.class fromJSONDictionary:values error:&error];
+ expect(model).beNil();
+ expect(error).notTo.beNil();
+ expect(error.domain).to.equal(MTLJSONAdapterErrorDomain);
+ expect(error.code).to.equal(MTLJSONAdapterErrorInvalidJSONMapping);
+});
+
it(@"should support key paths across arrays", ^{
NSDictionary *values = @{
@"users": @[
View
14 MantleTests/MTLManagedObjectAdapterSpec.m
@@ -226,6 +226,20 @@
expect(error).notTo.beNil();
});
+ it(@"should return nil and error with an illegal JSON mapping", ^{
+ MTLParent *parent = [MTLParent insertInManagedObjectContext:context];
+ expect(parent).notTo.beNil();
+
+ parent.string = @"foobar";
+
+ NSError *error = nil;
+ MTLIllegalManagedObjectMappingModel *model = [MTLManagedObjectAdapter modelOfClass:MTLIllegalManagedObjectMappingModel.class fromManagedObject:parent error:&error];
+ expect(model).beNil();
+ expect(error).notTo.beNil();
+ expect(error.domain).to.equal(MTLManagedObjectAdapterErrorDomain);
+ expect(error.code).to.equal(MTLManagedObjectAdapterErrorInvalidManagedObjectMapping);
+ });
+
it(@"should return an error if model doesn't validate for insert", ^{
MTLParentIncorrectTestModel *parentModel = [MTLParentIncorrectTestModel modelWithDictionary:@{} error:NULL];
View
4 MantleTests/MTLTestModel.h
@@ -62,3 +62,7 @@ extern const NSInteger MTLTestModelNameMissing;
// Returns a default name of 'foobar' when validateName:error: is invoked
@interface MTLSelfValidatingModel : MTLValidationModel
@end
+
+// Maps a non-existant property "name" to the "username" key in JSON.
+@interface MTLIllegalJSONMappingModel : MTLModel <MTLJSONSerializing>
+@end
View
10 MantleTests/MTLTestModel.m
@@ -172,3 +172,13 @@ - (BOOL)validateName:(NSString **)name error:(NSError **)error {
}
@end
+
+@implementation MTLIllegalJSONMappingModel
+
++ (NSDictionary *)JSONKeyPathsByPropertyKey {
+ return @{
+ @"name": @"username"
+ };
+}
+
+@end
Something went wrong with that request. Please try again.