Skip to content

Instantly share code, notes, and snippets.

@tapi
Created April 25, 2012 11:37
Show Gist options
  • Save tapi/2489095 to your computer and use it in GitHub Desktop.
Save tapi/2489095 to your computer and use it in GitHub Desktop.
Dictionary <--> Object Mapper for Objective-C
//
// NSObject+SMDictionaryMapping.h
// SoundTrack
//
// Created by Paddy O'Brien on 12-04-24.
// Copyright (c) 2012 Paddy O'Brien. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol SMMappedObject <NSObject>
+ (NSDictionary *)mappedKeys;
@end
@interface NSObject (SMDictionaryMapping)
- (id)initWithDictionary:(NSDictionary *)dictionary;
- (void)updateWithDictionary:(NSDictionary *)dictionary;
@end
//
// NSObject+DictionaryMapping.m
// SoundTrack
//
// Created by Paddy O'Brien on 12-04-24.
// Copyright (c) 2012 Paddy O'Brien. All rights reserved.
//
#import "NSObject+DictionaryMapping.h"
@interface NSObject ()
- (void)mapDictionaryToProperties:(NSDictionary *)dictionary;
@end
@implementation NSObject (DictionaryMapping)
- (id)initWithDictionary:(NSDictionary *)dictionary
{
if (self) {
[self mapDictionaryToProperties:dictionary];
}
return self;
}
- (void)updatePropertiesWithDictionary:(NSDictionary *)dictionary
{
[self mapDictionaryToProperties:dictionary];
}
- (void)mapDictionaryToProperties:(NSDictionary *)dictionary
{
NSArray* keys = [dictionary allKeys];
for (NSString *key in keys) {
NSString *mappedKey = key;
if ([self conformsToProtocol:@protocol(SMMappedObject)] && [self respondsToSelector:@selector(mappedKeys)]) {
NSDictionary *mappedKeys = [self performSelector:@selector(mappedKeys)];
if ([mappedKeys valueForKey:key]) {
mappedKey = [mappedKeys valueForKey:key];
}
}
[self setValue:[dictionary valueForKey:key] forKey:mappedKey];
}
}
@end
//
// NSObject+SMDictionaryMapping.h
//
// Created by Paddy O'Brien on 12-04-24.
// Copyright (c) 2012 Paddy O'Brien. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol SMMappedObject <NSObject>
+ (NSDictionary *)mappedKeys;
@end
@interface NSObject (SMDictionaryMapping)
- (id)initWithDictionary:(NSDictionary *)dictionary;
- (void)updateWithDictionary:(NSDictionary *)dictionary;
@end
//
// NSObject+DictionaryMapping.m
//
// Created by Paddy O'Brien on 12-04-24.
// Copyright (c) 2012 Paddy O'Brien. All rights reserved.
//
#import "NSObject+DictionaryMapping.h"
@interface NSObject ()
- (void)mapDictionaryToProperties:(NSDictionary *)dictionary;
@end
@implementation NSObject (DictionaryMapping)
- (id)initWithDictionary:(NSDictionary *)dictionary
{
if (self) {
[self mapDictionaryToProperties:dictionary];
}
return self;
}
- (void)updatePropertiesWithDictionary:(NSDictionary *)dictionary
{
[self mapDictionaryToProperties:dictionary];
}
- (void)mapDictionaryToProperties:(NSDictionary *)dictionary
{
NSArray* keys = [dictionary allKeys];
for (NSString *key in keys) {
NSString *mappedKey = key;
if ([self conformsToProtocol:@protocol(SMMappedObject)] && [self respondsToSelector:@selector(mappedKeys)]) {
NSDictionary *mappedKeys = [self performSelector:@selector(mappedKeys)];
if ([mappedKeys valueForKey:key]) {
mappedKey = [mappedKeys valueForKey:key];
}
}
id value = ([[dictionary valueForKey:key] isKindOfClass:[NSNull class]]) ? nil : [dictionary valueForKey:key];
[self setValue:value forKey:mappedKey];
}
}
@end
@ashfurrow
Copy link

Well, your current approach would work except if you're being passed in a sub-entity (like the user model of a photo model). So you could do something like this:

-(void)setUserModel:(id)theUserModel
{
    if ([theUserModel isKindOfClass:[NSDictionary class]])
    {
        self.userModel = [[UserModel alloc] initWithDictionary:theUserModel];
        return;
    }

    _userModel = theUserModel;
}

Thoughts?

@tapi
Copy link
Author

tapi commented Apr 26, 2012

Yes but what if you actually want a dictionary as a property on an object? Interogate the class of the property to see if it conforms to the protocol perhaps?

@warwick an example might look like this

@interface Contact : NSObject <SMMappedObject>
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, strong) Email *email;
@end

Later we want to get a bunch of contacts from some webservice that spits back JSON

- (NSArray *)getMyContacts
{
    NSData *JSONData = [MagicWebRequestThatRequiresNoSetup GetURL:@"www.myaddressbook.com/contacts"];
    NSArray *contacts = [JSONData parseObjectFromJSON];

    NSMutableArray* retval =  [NSMutableArray array];
    for (NSDictionary *contactData in contact) {
        Contact *newContact = [[Contact alloc] initWithDictionary:contactData];
        [retval addObject:newContact];
    }

    return retval;
}

The idea is to have you local domain object populated without having to slog through writing your own mappings.

@aaronpeterson
Copy link

I'm curious about sub-entities and arrays of sub-entities, too. Would it be best to define those in the model class as an array of strings (JSON key names) you want explicitly mapped to sub-entities? One thing all of my JSON keys have in common is that the sub-entity keys are all upper-case first character. This could be the "auto" mode; if a mapped key is upper-case first character it looks for a model class of same name and maps accordingly. I haven't looked to see what something like RestKit does. Just seems like something that could be a single NSObject category rather than a giant library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment