Skip to content

Commit 62581ee

Browse files
committed
Add a type-mismatch error to give more detail when the JSON is invalid because of the wrong type.
1 parent c4db2f4 commit 62581ee

7 files changed

Lines changed: 123 additions & 4 deletions

File tree

JSONModel/JSONModel/JSONModel.m

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,9 @@ -(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
293293
if (property.protocol) {
294294

295295
//JMLog(@"proto: %@", p.protocol);
296-
jsonValue = [self __transform:jsonValue forProperty:property];
296+
jsonValue = [self __transform:jsonValue forProperty:property error:err];
297297
if (!jsonValue) {
298-
if (err) *err = [JSONModelError errorInvalidData];
298+
if ((err != nil) && (*err == nil)) *err = [JSONModelError errorInvalidData];
299299
return nil;
300300
}
301301
}
@@ -554,7 +554,7 @@ -(void)__restrospectProperties
554554

555555
#pragma mark - built-in transformer methods
556556
//few built-in transformations
557-
-(id)__transform:(id)value forProperty:(JSONModelClassProperty*)property
557+
-(id)__transform:(id)value forProperty:(JSONModelClassProperty*)property error:(NSError**)err
558558
{
559559
Class protocolClass = NSClassFromString(property.protocol);
560560
if (!protocolClass) {
@@ -574,7 +574,18 @@ -(id)__transform:(id)value forProperty:(JSONModelClassProperty*)property
574574

575575
//check if it's a list of models
576576
if ([property.type isSubclassOfClass:[NSArray class]]) {
577-
577+
578+
// Expecting an array, make sure 'value' is an array
579+
if(![[value class] isSubclassOfClass:[NSArray class]])
580+
{
581+
if(err != nil)
582+
{
583+
NSString* mismatch = [NSString stringWithFormat:@"Property '%@' is declared as NSArray<%@>* but the corresponding JSON value is not a JSON Array.", property.name, property.protocol];
584+
*err = [JSONModelError errorInvalidDataWithTypeMismatch:mismatch];
585+
}
586+
return nil;
587+
}
588+
578589
if (property.convertsOnDemand) {
579590
//on demand conversion
580591
value = [[JSONModelArray alloc] initWithArray:value modelClass:[protocolClass class]];
@@ -587,6 +598,18 @@ -(id)__transform:(id)value forProperty:(JSONModelClassProperty*)property
587598

588599
//check if it's a dictionary of models
589600
if ([property.type isSubclassOfClass:[NSDictionary class]]) {
601+
602+
// Expecting a dictionary, make sure 'value' is a dictionary
603+
if(![[value class] isSubclassOfClass:[NSDictionary class]])
604+
{
605+
if(err != nil)
606+
{
607+
NSString* mismatch = [NSString stringWithFormat:@"Property '%@' is declared as NSDictionary<%@>* but the corresponding JSON value is not a JSON Object.", property.name, property.protocol];
608+
*err = [JSONModelError errorInvalidDataWithTypeMismatch:mismatch];
609+
}
610+
return nil;
611+
}
612+
590613
NSMutableDictionary* res = [NSMutableDictionary dictionary];
591614
JSONModelError* initErr = nil;
592615

JSONModel/JSONModel/JSONModelError.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ extern NSString* const JSONModelErrorDomain;
3838
*/
3939
extern NSString* const kJSONModelMissingKeys;
4040

41+
/**
42+
* If JSON input has a different type than expected by the model, check the
43+
* userInfo dictionary of the JSONModelError instance you get back -
44+
* under the kJSONModelTypeMismatch key you will find a description
45+
* of the mismatched types.
46+
*/
47+
extern NSString* const kJSONModelTypeMismatch;
48+
4149
/////////////////////////////////////////////////////////////////////////////////////////////
4250
/**
4351
* Custom NSError subclass with shortcut methods for creating
@@ -58,6 +66,12 @@ extern NSString* const kJSONModelMissingKeys;
5866
*/
5967
+(id)errorInvalidDataWithMissingKeys:(NSSet*)keys;
6068

69+
/**
70+
* Creates a JSONModelError instance with code kJSONModelErrorInvalidData = 1
71+
* @param A description of the type mismatch that was encountered.
72+
*/
73+
+(id)errorInvalidDataWithTypeMismatch:(NSString*)mismatchDescription;
74+
6175
/**
6276
* Creates a JSONModelError instance with code kJSONModelErrorBadResponse = 2
6377
*/

JSONModel/JSONModel/JSONModelError.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
NSString* const JSONModelErrorDomain = @"JSONModelErrorDomain";
2020
NSString* const kJSONModelMissingKeys = @"kJSONModelMissingKeys";
21+
NSString* const kJSONModelTypeMismatch = @"kJSONModelTypeMismatch";
2122

2223
@implementation JSONModelError
2324

@@ -35,6 +36,13 @@ +(id)errorInvalidDataWithMissingKeys:(NSSet *)keys
3536
userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON data. Required JSON keys are missing from the input. Check the error user information.",kJSONModelMissingKeys:[keys allObjects]}];
3637
}
3738

39+
+(id)errorInvalidDataWithTypeMismatch:(NSString*)mismatchDescription
40+
{
41+
return [JSONModelError errorWithDomain:JSONModelErrorDomain
42+
code:kJSONModelErrorInvalidData
43+
userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON data. The JSON type mismatches the expected type. Check the error user information.",kJSONModelTypeMismatch:mismatchDescription}];
44+
}
45+
3846
+(id)errorBadResponse
3947
{
4048
return [JSONModelError errorWithDomain:JSONModelErrorDomain
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"singleImage": {"idImage": 2, "name": "lake.jpg"},
3+
4+
"images": {
5+
"x" : {"idImage": 1, "name": "house.jpg", "copyright":{"author":"Marin Todorov", "year":2012} },
6+
"y" : {"idImage": 2, "name": "lake.jpg"},
7+
"z" : {"idImage": 3, "name": "peak.jpg"}
8+
},
9+
10+
"imagesObject": {
11+
"image2": {"idImage": 2, "name": "lake.jpg"},
12+
"image3": {"idImage": 3, "name": "peak.jpg"}
13+
}
14+
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"singleImage": {"idImage": 2, "name": "lake.jpg"},
3+
4+
"images": [
5+
{"idImage": 1, "name": "house.jpg", "copyright":{"author":"Marin Todorov", "year":2012} },
6+
{"idImage": 2, "name": "lake.jpg"},
7+
{"idImage": 3, "name": "peak.jpg"}
8+
],
9+
10+
"imagesObject": [
11+
{"idImage": 2, "name": "lake.jpg"},
12+
{"idImage": 3, "name": "peak.jpg"}
13+
]
14+
15+
}

JSONModelDemoTests/UnitTests/SimpleDataErrorTests.m

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,42 @@ -(void)testMissingKeysError
3333
STAssertTrue([missingKeys[1] isEqualToString:@"longNumber"],@"missing field longNumber not found in missingKeys");
3434
}
3535

36+
-(void)testTypeMismatchErrorImages
37+
{
38+
NSString* filePath = [[NSBundle bundleForClass:[JSONModel class]].resourcePath stringByAppendingPathComponent:@"nestedDataWithTypeMismatchOnImages.json"];
39+
NSString* jsonContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
40+
41+
STAssertNotNil(jsonContents, @"Can't fetch test data file contents.");
42+
43+
NSError* err;
44+
NestedModel* p = [[NestedModel alloc] initWithString: jsonContents error:&err];
45+
STAssertNil(p, @"Model is not nil, when input is invalid");
46+
STAssertNotNil(err, @"No error when types mismatch.");
47+
48+
STAssertTrue(err.code == kJSONModelErrorInvalidData, @"Wrong error for type mismatch");
49+
NSString* mismatchDescription = err.userInfo[kJSONModelTypeMismatch];
50+
STAssertTrue(mismatchDescription, @"error does not have kJSONModelTypeMismatch key in user info");
51+
STAssertTrue([mismatchDescription rangeOfString:@"'images'"].location != NSNotFound, @"error should mention that the 'images' property (expecting an Array) is mismatched.");
52+
}
53+
54+
-(void)testTypeMismatchErrorImagesObject
55+
{
56+
NSString* filePath = [[NSBundle bundleForClass:[JSONModel class]].resourcePath stringByAppendingPathComponent:@"nestedDataWithTypeMismatchOnImagesObject.json"];
57+
NSString* jsonContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
58+
59+
STAssertNotNil(jsonContents, @"Can't fetch test data file contents.");
60+
61+
NSError* err;
62+
NestedModel* p = [[NestedModel alloc] initWithString: jsonContents error:&err];
63+
STAssertNil(p, @"Model is not nil, when input is invalid");
64+
STAssertNotNil(err, @"No error when types mismatch.");
65+
66+
STAssertTrue(err.code == kJSONModelErrorInvalidData, @"Wrong error for type mismatch");
67+
NSString* mismatchDescription = err.userInfo[kJSONModelTypeMismatch];
68+
STAssertTrue(mismatchDescription, @"error does not have kJSONModelTypeMismatch key in user info");
69+
STAssertTrue([mismatchDescription rangeOfString:@"'imagesObject'"].location != NSNotFound, @"error should mention that the 'imagesObject' property (expecting a Dictionary) is mismatched.");
70+
}
71+
3672
-(void)testBrokenJSON
3773
{
3874
NSString* jsonContents = @"{[1,23,4],\"123\":123,}";

JSONModelDemo_iOS.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
697852FD17D934B5006BFCD0 /* nestedDataWithTypeMismatchOnImages.json in Resources */ = {isa = PBXBuildFile; fileRef = 697852FC17D934B5006BFCD0 /* nestedDataWithTypeMismatchOnImages.json */; };
11+
697852FF17D93547006BFCD0 /* nestedDataWithTypeMismatchOnImagesObject.json in Resources */ = {isa = PBXBuildFile; fileRef = 697852FE17D93546006BFCD0 /* nestedDataWithTypeMismatchOnImagesObject.json */; };
1012
9C0D0240166E6BBF001EA645 /* KivaViewControllerNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C0D023E166E6BBF001EA645 /* KivaViewControllerNetworking.m */; };
1113
9C0D0241166E6BBF001EA645 /* KivaViewControllerNetworking.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9C0D023F166E6BBF001EA645 /* KivaViewControllerNetworking.xib */; };
1214
9C66DFA8168CEF420015CCDF /* ArrayTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C66DF60168CEF420015CCDF /* ArrayTests.m */; };
@@ -142,6 +144,8 @@
142144
/* End PBXContainerItemProxy section */
143145

144146
/* Begin PBXFileReference section */
147+
697852FC17D934B5006BFCD0 /* nestedDataWithTypeMismatchOnImages.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = nestedDataWithTypeMismatchOnImages.json; sourceTree = "<group>"; };
148+
697852FE17D93546006BFCD0 /* nestedDataWithTypeMismatchOnImagesObject.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = nestedDataWithTypeMismatchOnImagesObject.json; sourceTree = "<group>"; };
145149
9C0D023D166E6BBF001EA645 /* KivaViewControllerNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KivaViewControllerNetworking.h; sourceTree = "<group>"; };
146150
9C0D023E166E6BBF001EA645 /* KivaViewControllerNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KivaViewControllerNetworking.m; sourceTree = "<group>"; };
147151
9C0D023F166E6BBF001EA645 /* KivaViewControllerNetworking.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = KivaViewControllerNetworking.xib; sourceTree = "<group>"; };
@@ -414,6 +418,8 @@
414418
9C66DF6B168CEF420015CCDF /* jsonTypes.json */,
415419
9C66DF6C168CEF420015CCDF /* nestedData.json */,
416420
9C66DF6D168CEF420015CCDF /* nestedDataWithErrors.json */,
421+
697852FC17D934B5006BFCD0 /* nestedDataWithTypeMismatchOnImages.json */,
422+
697852FE17D93546006BFCD0 /* nestedDataWithTypeMismatchOnImagesObject.json */,
417423
9C66DF6E168CEF420015CCDF /* post.json */,
418424
9C66DF6F168CEF420015CCDF /* primitives.json */,
419425
9C66DF70168CEF420015CCDF /* primitivesWithErrors.json */,
@@ -846,6 +852,8 @@
846852
9C66DFB5168CEF420015CCDF /* withOptProp.json in Resources */,
847853
9C66DFB6168CEF420015CCDF /* withoutOptProp.json in Resources */,
848854
9CB1EE47172C1205004BAA07 /* specialPropertyName.json in Resources */,
855+
697852FD17D934B5006BFCD0 /* nestedDataWithTypeMismatchOnImages.json in Resources */,
856+
697852FF17D93547006BFCD0 /* nestedDataWithTypeMismatchOnImagesObject.json in Resources */,
849857
);
850858
runOnlyForDeploymentPostprocessing = 0;
851859
};

0 commit comments

Comments
 (0)