1

I need to set a bunch of object attributes according to an NSDictionary supplied via JSON from a remote server. I don't want to overwrite attributes that are not in the dictionary.

Since there lot of attributes, I have long series of statements that look like this:

if (dictionary[@"address_1"] != [NSNull null]) 
    self.configuration.userAddress1 = dictionary[@"address_1"];

The keys in the dictionary are not identical to the names of the properties; there are two different systems that grew up separately that I'm trying to make work together.

Maybe I've been doing too much Ruby coding, but it seems like there should be a better idiom in Objective-C for doing this. Any ideas?

John
  • 2,121
  • 1
  • 23
  • 38
  • I should have mentioned that the attribute names and dictionary key names are not exactly the same: userAddress1 vs address_1, for example. That's because they are two different systems that grew up separately, trying to work together. So I am doing a bit of 'translation' in the process. – John Jan 15 '15 at 20:29
  • That's a pretty important point, because if it weren't true, this would be as simple as [using `setValuesForKeysWithDictionary:`](http://stackoverflow.com/questions/6299007/dynamically-populate-object-properties-with-dictionary-values/6299057#6299057). However, you could still use that if you transform the dictionary keys _first_. – jscs Jan 15 '15 at 20:33
  • H'mm. Now that's a thought. – John Jan 15 '15 at 20:44
  • Thanks for all the ideas, but as some have noted below, the cure could be worse than the disease. I will stick with the above. – John Jan 16 '15 at 00:01

4 Answers4

3

Sounds like you want a simple mapping solution - you can hand roll one like this

[@{
   @"address_1" : @"address1",
   @"address_2" : @"address2",
   ...
   } enumerateKeysAndObjectsUsingBlock:^(NSString *remoteKey, NSString *localKey, BOOL *stop) {
     id remoteValue = dictionary[remoteKey];

     if (![remoteValue isEqual:NSNull.null]) {
       [self.configuration setValue:remoteValue forKey:localKey];
     }
   }];

This applies some basic Null checking logic and allows the remote/local objects to have different property names

Paul.s
  • 38,494
  • 5
  • 70
  • 88
1

No, it is not to rubyish, because Objective-C is dynamically typed. You can do this with key value coding:

for (NSString *key in dictionary)
{
    id value = dictionary[key];
    [self.configuration setValue:value forKey:key];
}

But see my comments here.

BTW: If a key does not exist in a dictionary, the result is nil not [NSNull null].

So, if you do not want to set properties that are not in the dictionary, you have to do nothing additional. If you do not want to set properties that are in the dictionary with the value [NSNull null], you still can add the check.

If you do not want to set null's:

for (NSString *key in dictionary)
{
  id value = dictionary[key];
  if (value != [NSNull null] )
  {
    [self.configuration setValue:value forKey:key];
  }
}

If you do want to set null's with nil:

for (NSString *key in dictionary)
{
  id value = dictionary[key];
  if (value == [NSNull null] )
  {
    value = nil;
  }
  [self.configuration setValue:value forKey:key];
}
Community
  • 1
  • 1
Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50
  • 1
    But it's not uncommon for dictionaries created from JSON parsing to contain `NSNull` values. And it seems the OP wants to check for `NSNull` values and act accordingly. – rmaddy Jan 15 '15 at 20:11
  • No, it is not uncommon, but he says: "I don't want to overwrite attributes that are not in dictionary." He does not say: "I don't want to overwrite attributes that are with `null` in dictionary." (And probably, if there is a key with the value `[NSNull null]` in the dictionary you want to set the property to `nil`.) But I added a clarification. – Amin Negm-Awad Jan 15 '15 at 20:14
  • Yes, the reason I test for [NSNull null] is because the dictionary does contain that kind of null. Testing for nil doesn't work. Another complication is that the key names in the dictionary and attribute names are not exactly the same (for historical reasons). – John Jan 15 '15 at 20:24
  • 1
    Yes, `nil` and `NSNull` are very different things, @John. `nil` is literally "no object": `(id)NULL`. `NSNull` _is_ an object, but it's the "no value" placeholder for Cocoa collections. – jscs Jan 15 '15 at 20:54
  • 1
    @John That's why I linked to my comments to the other Q. A generic solution is very sexy at the beginning. But then you have to keep a dictionary, because keys changed, you have to do checks on values of the keys, you lose security in depth at a place, where you could have it … At the end of the day you find yourself defining a kind of DSL. Not worth. – Amin Negm-Awad Jan 15 '15 at 21:01
  • I'm marking the comment above as the answer... not worth it! – John Jan 16 '15 at 00:02
1

One possible idiom in Objective-C would be: don't have a lot of object attributes. Have one attribute, a dictionary! Now it's easy to populate that dictionary based on the incoming dictionary.

matt
  • 515,959
  • 87
  • 875
  • 1,141
0

Another approach would be to override -setValue:forUndefinedKey: in your class and map the keys there.

- (void) setValue:(id)value forUndefinedKey:(NSString*)key
{
    static dispatch_once_t once;
    static NSDictionary* map;
    dispatch_once(&once, ^{
        map = @{
                 @"address_1" : @"address1",
                 @"address_2" : @"address2",
                 // ...
               };
    });

    NSString* newKey = map[key];
    if (newKey)
        [self setValue:value forKey:newKey];
    else
        [super setValue:value forUndefinedKey:key];
}

Now you can use -setValuesForKeysWithDictionary: with the original dictionary. That method will do the substitution of nil for NSNull objects, so you have to decide if that's what you want.

You can go the other direction, too, if desired, by overriding -valueForUndefinedKey:.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154