I am consuming JSON that is constantly evolving. I started writing code to consume the JSON using Mantle lately. It seemed like a very good choice for what I want to do. However, it seems that if the JSON being consumed has properties that do not exist in the model, the JSON transformation fails. I'm using the [MTLJSONAdapter modelOfClass:fromJSONDictionary:error:]; call to map the JSON.
Thanks in advance,
Upon closer inspection of the code, Mantle does require that all json properties map to something in the model. Otherwise, what will happen is an exception will be thrown for that property.
inside of MTLValidateAndSetValue of MTLModel, it doesn't check for the existence of the property before it sets it.
#try {
if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
if (forceUpdate || value != validatedValue) {
[obj setValue:validatedValue forKey:key];
}
return YES;
} #catch (NSException *ex) {
NSLog(#"*** Caught exception setting key \"%#\" : %#", key, ex);
// Fail fast in Debug builds.
#if DEBUG
#throw ex;
#else
if (error != NULL) {
*error = [NSError mtl_modelErrorWithException:ex];
}
return NO;
#endif
}
This is problematic if the JSON you are consuming isn't guaranteed to match your model.
I ended up doing a custom JSON representation for my work having the constructor build the object based on the incoming JSON rather than against the model. It will first iterate over the json properties and tries to map them directly to the model properties using implicit mapping. If there are any properties that require special handling, it is up to the subclass to override the init call and apply the transformation manually.
Related
I have this code in ObjC/C:
AVCaptureFocusMode GetFocusModeWithString(NSString *mode) {
if ([mode isEqualToString:#"locked"]) {
return AVCaptureFocusModeLocked;
} else if ([mode isEqualToString:#"auto"]) {
return AVCaptureFocusModeAutoFocus;
} else {
#throw [NSError errorWithDomain: #"FocusError", code: 12];
}
}
It used to be working fine when calling from ObjC code. Now I am rewriting the caller side using swift. However, in swift, this code does not actually throw:
// does not work, swift thinks this function does not throw.
do {
try GetFocusModeWithString(#"auto")
}
Am I doing anything wrong here? Is the ObjC code bad? how can I improve the ObjC code to work nicely with swift?
Objective C does not have do/try/catch semantics the way that Swift does.
#throw causes a runtime exception. This isn't something you can catch in Swift.
The way that you can integrate Cocoa error handling with Swift is described in the Swift Objective C interoperability documentation and this answer
First declare your Objective-C function to accept a pointer to an instance of NSError. Then you can flag that parameter with function with __attribute__((swift_error(nonnull_error))).
This will cause a Swift exception if the error is non-null when the function returns
- (AVCaptureFocusMode) getFocusModeWithString:(NSString *) mode error: (NSError **)error __attribute__((swift_error(nonnull_error))); {
*error = nil;
if ([mode isEqualToString:#"locked"]) {
return AVCaptureFocusModeLocked;
} else if ([mode isEqualToString:#"auto"]) {
return AVCaptureFocusModeAutoFocus;
}
*error = [NSError errorWithDomain:#"FocusError" code:12 userInfo:nil];
return -1;
}
Now, in your Swift code you can call it with
do {
let focusMode = try getFocusMode(with: "auto")
} catch {
print(error)
}
Note that this will change your method signature in Objective-C; You will need to pass &error to the method and check its value on return.
Objective-C exceptions are not compatible with Swift. Here's what Apple says:
Handle Exceptions in Objective-C Only
In Objective-C, exceptions are distinct from errors. Objective-C exception handling uses the #try, #catch, and #throw syntax to indicate unrecoverable programmer errors. This is distinct from the Cocoa pattern—described above—that uses a trailing NSError parameter to indicate recoverable errors that you plan for during development.
In Swift, you can recover from errors passed using Cocoa’s error pattern, as described above in Catch Errors. However, there’s no safe way to recover from Objective-C exceptions in Swift. To handle Objective-C exceptions, write Objective-C code that catches exceptions before they reach any Swift code.
Is there any way of catching an Objective-C exception that was thrown as a result of a JavaScript script evaluation?
For example:
I have a class Obj with a method canThrow that I have exported through JSExport. From some script I call this method and it indeed throws an exception.
Any way I can handle it?
I have already tried to wrap the script evaluation code in a try-catch, but that didn't help.
Your question is a little bit unclear, but I will try to answer it anyway.
If you need to raise an exception from Objective-C to Javascript, you should use -[JSContext exception] property. See following question for details.
Passing exception from Javascript to Objective-C is straightforward, you simply export some method, that will handle exception in Objective-C like:
myJSContext[#"PassExceptionToObjC"] = ^void(JSValue *)jsException {
// Handle exception
}
And then use following code in Javascript:
try {
// some code
}
catch (exception) {
PassExceptionToObjC(exception);
}
Alternatively you can return specific value from your Javascript to Objective-C. Like:
function doSomething() {
try {
// Do something
return 'Ok';
}
catch (error) {
return 'Error happens ' + error.message;
}
}
In case you need to avoid throwing Objective-C exception in method, called from Javascript, you simply should add #try/#catch in your Objective-C method or block:
myJSContext[#"DoSomething"] = ^void(JSValue *)someValue {
#try {
// Do something
}
#catch (NSException *exception) {
// Handle exception
}
}
Or, preferred, change logic of Objective-C code, that trigger exception.
Similar way applies if you need to avoid exception in Javascript, called from Objective-C. You simply add try/catch. In most cases you may simply ignore exceptions in JavaScript, called from Objective-C. But I suggest to implement exception handing on Objective-C side for any non-trivial Javascript, at least to simplify troubleshooting.
Hope it helps.
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.
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
Is there a way to write some sort of catch statement around ([MyArray ObjectAtIndex:myindexpath.row])so that I can run this without throwing an exception?
In other words, I want to write this sort of expression:
if ([MyArray ObjectAtIndex:myindexpath.row]) {
// do some stuff if the object is in the array
else {
// do some other stuff
}
Sure: use logic & maths.
if (index < myArray.count) {
// ...
}