iosobjective-cjsonnsstringobjective-c-category

Objective C: Is it good practice to check JSON values if it is the type you expect?


Currently, I am checking if the JSON value is an NSString and if it isn't, assign the property a default string. This way I can prevent the app from crashing if the JSON value is null and I can return a User object with a default first name and last name.

// User model

+ (Email *)getEmailInfoWithDictionary:(id)dict {
  Email *email = [[self alloc] init];
  if ([dict isKindOfClass:[NSDictionary class]]) {
    user.firstname = [NSString checkType:dict[@"firstname"] defaultString:@"John"];
    user.lastname = [NSString checkType:dict[@"lastname"] defaultString:@"Smith"];
  }
}
return user;
}

// NSString category method.

+ (NSString *)checkType:(id)obj defaultString:(NSString *)def {
  if (obj == nil || ![obj isKindOfClass:[NSString class]]) {
    return def;
  }
  return obj;
}

I have a couple of concerns, however. Is it a good idea to always check for null values regarding json values so this way you won't get crashes? I've noticed some Objective C tutorials that don't do this. Makes me wonder if I shouldn't worry about this and I should expect the API to return the correct values. My second concern is the method I am using a good idea or is a there better way?


Solution

  • I would say start by leaning into Objective-C's default handling of nil. In most cases, nil will do what you want without crashing. In contrast, [NSNull null] is poorly designed and a constant problem.

    @interface NSObject (MyCasting)
    - (NSString *)stringOrNil;
    - (NSNumber *)numberOrNil;
    - (NSDictionary *)dictionaryOrNil;
    - (NSArray *)arrayOrNil;
    @end
    
    @implementation NSObject (MyCasting)
    
    - (NSString *)stringOrNil {
        return [self isKindOfClass:NSString.class] ? (NSString *)self : nil;
    }
    
    - (NSNumber *)numberOrNil {
        return [self isKindOfClass:NSNumber.class] ? (NSNumber *)self: nil;
    }
    
    - (NSDictionary *)dictionaryOrNil {
        return [self isKindOfClass:NSDictionary.class] ? (NSDictionary *)self: nil;
    }
    
    - (NSArray *)arrayOrNil {
        return [self isKindOfClass:NSArray.class] ? (NSArray *)self: nil;
    }
    
    @end
    

    This will provide you with a good level of safety using nil as the default value.

    + (Email *)getEmailInfoWithDictionary:(id)dict {
        Email *email = [[self alloc] init];
        NSDictionary *dictionary = [dict dictionaryOrNil];
    
        email.firstname = [dictionary[@"firstname"] stringOrNil];
        email.lastname = [dictionary[@"lastname"] stringOrNil];
    
        return email;
    }
    

    If you feel you need the additional safety of object default values, then you can use the ?: operator.

    email.firstname = [dictionary[@"firstname"] stringOrNil] ?: "";
    email.lastname = [dictionary[@"lastname"] stringOrNil] ?: "";