2

Currently I am in the process of working with an API that is still in development. Due to this, the keys in the response are still changing. I have successfully been able to retrieve and parse the JSON data from the API into an NSDictionary, and then use this NSDictionary to map the values into custom objects. The approach I am using is the following

-(id)initWithDictionary:(NSDictionary*)dictionary
{
    if(self = [super init]){
        _ID = [dictionary valueForKey:kKEY_ID];
        _name = [dictionary valueForKey:kKEY_NAME];
        _nestedObject = [[NestedObject alloc]initWithDictionary:[dictionary valueForKey:kKEY_NESTED_OBJECT]];
        //etc...
    }
    return self
}

Each nested object also contains the same parsing structure.

This works fine except for when the API changes. When something does change, required values do not exist and this causes unexpected behavior or crashes.

Ideally, if one of the keys change, I would like to produce a NSError that I can use to print the value that has changed helping me more quickly find the change and rectify it.

The only alternative approach that I have currently been able to come up with I feel is messy and unmaintainable.

-(id)initWithDictionary:(NSDictionary*)dictionary andError:(NSError**)error
{
    if(self = [super init]){

        BOOL _parsedSuccessfully = TRUE;

        if (_parsedSuccessfully) {
             _ID = [dictionary valueForKey: kKEY_ID];

             if (!_ID){
                 _parsedSuccessfully = FALSE;
                 *error = [NSError parsingErrorFromKey: kKEY_ID];
             }
        }

        if (_parsedSuccessfully) {
             _name = [dictionary valueForKey: kKEY_NAME];

             if (!_name){
                 _parsedSuccessfully = FALSE;
                 *error = [NSError parsingErrorFromKey: kKEY_NAME];
             }
        }

        if (_parsedSuccessfully) {
             _nestedObject = [[NestedObject alloc]initWithDictionary:[dictionary valueForKey:kKEY_NESTED_OBJECT]];

             if (!_nestedObject){
                 _parsedSuccessfully = FALSE;
                 *error = [NSError parsingErrorFromKey: kKEY_NESTED_OBJECT];
             }
        }

        //etc...

        if (!_parsedSuccessfully) {
            return nil;
        }
    }
    return self
}

I was wondering if anyone else had any other better approaches that preferably uses much less duplication.

Any help would be greatly appreciated.

2 Answers 2

1

Add an isValid method to your object, which can be used in any situation, not just when initialised from the JSON dictionary.

- (BOOL)isValid:(NSError **)error {

    #define CHECK_NOT_NULL(x, key) if (!x) { \
        if (error != NULL) \
            *error = [NSError parsingErrorFromKey:key]; \
        return NO; \
    }

    #define CHECK_NOT_EMPTY(x, key) if (!x || ![x length]) { \
        if (error != NULL) \
            *error = [NSError parsingErrorFromKey:key]; \
        return NO; \
    }

    CHECK_NOT_NULL(_ID, kKEY_ID);
    CHECK_NOT_EMPTY(_name, kKEY_NAME);
    // etc.

    return YES;
    #undef CHECK_NOT_NULL
    #undef CHECK_NOT_EMPTY
}

And then use this in your init method:

- (id)initWithDictionary:(NSDictionary*)dictionary andError:(NSError**)error
{
    if (self = [super init]) {
         _ID = [dictionary valueForKey: kKEY_ID];
         _name = [dictionary valueForKey: kKEY_NAME];
         // etc.

         if (![self isValid:error]) {
             self = nil;    // Assuming ARC
         }
    }
    return self;
}
Sign up to request clarification or add additional context in comments.

3 Comments

This solution seems to be exactly what I what I was looking for. I like how it gracefully handles sub-objects that failed parsing. And I also find your use of the Defined functions very interesting and maintainable. Thanks for the help!
@trojanfoe ...or he could call [self setValuesForKeysWithDictionary:dictionary]; and override setValue:forKey: if any conversions need to be done on the fly.
@trojanfoe Due to the update to your post that moved the null checks into the define functions, I was able to abstract the defined functions to my definition file so that they can be reused in each sub-object that I am parsing. This was extremely helpful.
0

If you create an array of your keys then you can run your check in a loop so you only have one copy of the loop.

Again, using the array you could get all of the keys from the dictionary and remove them from each other. One way will give you new keys and the other way will give you the missing keys.

1 Comment

I do really like this idea. However, the part I am struggling with at the moment is how the top layer objects know if a child object has failed creation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.