I am creating a helper to ease transforming snapshots to objects and viceversa. I haven't finished my project but it is working so far, I will update whenever I do changes.
What the class does is assign automatically the value for key, but if the key represents a dictionary, then it is mapped to another object again (which may be another class object)
The getMap method is pretty straight forward, converting each property to a dictionary or object. When the property is another object. You can't assign nil values so the transformation must be done to [NSNull].
I couldn't find a way to auto-detect BOOL / Double / int etc, so they should be mapped correctly on getMap method, or simply use NSNumbers in model the properties.
Interface
#import <Foundation/Foundation.h>
@import FirebaseDatabase;
#ifndef FIRModel_m
#define FIRModel_m
#define IS_OBJECT(T) _Generic( (T), id: YES, default: NO)
#endif
/** Firebase model that helps converting Firebase Snapshot to object, and converting the object
* to a dictionary mapping for updates */
@interface FIRModel : NSObject
/** Parses the snapshot data into the object */
- (void) parseFromSnapshot: (FIRDataSnapshot*) snapshot;
/** Returns a new model for the given key */
- (FIRModel*) modelForKey: (NSString*) key;
/** Returns the dictionary representation of this object */
- (NSMutableDictionary*) getMap;
/** Returns an object value for the given preference
* If the property is null, then NSNUll is returned
*/
- (NSObject*) objFor: (id) value;
@end
Implementation
#import "FIRModel.h"
@implementation FIRModel
/** Parses the snapshot data into the object */
- (void) parseFromSnapshot: (FIRDataSnapshot*) snapshot {
[self setValuesFromDictionary: snapshot.value];
}
/** Custom implementation for setValuesForKeysWithDictionary
* Whenever it finds a Dictionary, it is transformed to the corresponding model object
*/
- (void)setValuesFromDictionary:(NSDictionary*)dict
{
NSLog(@"Parsing in %@ the following received info: %@", [self class], dict);
for (NSString* key in dict) {
NSObject* value = [dict objectForKey:key];
if(!value || [value isKindOfClass: [NSNull class]]) {
//do nothing, value stays null
}
//TODO: Do the same for arrays
else if(value && [value isKindOfClass: [NSDictionary class]]) {
FIRModel* submodel = [self modelForKey: key];
if(submodel) {
[submodel setValuesFromDictionary: (NSDictionary*)value];
[self setValue: submodel forKey: key];
} else {
NSLog(@"ERROR - *** Nil model returned from modelForKey for key: %@ ***", key );
}
}
else {
[self setValue: value forKey:key];
}
}
}
/** Override for added firebase properties**/
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"Unknown key: %@ on object: %@", key, [self class] );
}
/** Returns a new model for the given key */
- (FIRModel*) modelForKey: (NSString*) key {
return nil; //to be implemented by subclasses
}
/** Returns the dictionary representation of this object */
- (NSMutableDictionary*) getMap {
[NSException raise:@"getMap not implmented" format:@"ERROR - Not implementing getMap for %@", self.class];
return [NSMutableDictionary dictionary];
}
/** Returns an object value for the given preference
* If the property is null, then NSNUll is returned
*/
- (NSObject*) objFor: (id) value {
if(!value || !IS_OBJECT(value)) {
return [NSNull null];
}
return value;
}
@end
Example usage:
#import <Foundation/Foundation.h>
#import "FIRModel.h"
/** The user object */
@class PublicInfo;
@interface User : FIRModel
@property (nonatomic, strong) NSString* email;
@property (nonatomic, strong) NSString* phone;
@property (nonatomic, strong) PublicInfo* publicInfo;
@property (nonatomic, assign) double aDoubleValue;
@property (nonatomic, assign) BOOL aBoolValue;
@property (nonatomic, strong) id timestampJoined; //Map or NSNumber
@property (nonatomic, strong) id timestampLastLogin; //Map or NSNumber
@end
@interface PublicInfo : FIRModel
@property (nonatomic, strong) NSString* key;
@property (nonatomic, strong) NSString* name;
@property (nonatomic, strong) NSString* pic;
@end
Implementation
#import "User.h"
@implementation User
/** Returns a new model for the given key */
- (FIRModel*) modelForKey: (NSString*) key {
if ([key isEqualToString: @"publicInfo"]) {
return [[PublicInfo alloc] init];
}
return nil;
}
- (NSMutableDictionary *)getMap {
NSMutableDictionary* map = [NSMutableDictionary dictionary];
map[@"email"] = [self objFor: self.email];
map[@"phone"] = [self objFor: self.phone];
map[@"aDoubleValue"] = @(self.aDoubleValue);
map[@"aBoolValue"] = @(self.aBoolValue);
map[@"publicInfo"] = self.publicInfo ? [self.publicInfo getMap] : [NSNull null];
map[@"timestampJoined"] = [self objFor: self.timestampJoined];
map[@"timestampLastLogin"] = [self objFor: self.timestampLastLogin];
return map;
}
@end
#pragma mark -
@implementation PublicInfo
- (NSMutableDictionary *)getMap {
NSMutableDictionary* map = [NSMutableDictionary dictionary];
map[@"name"] = [self objFor: self.name];
map[@"pic"] = [self objFor: self.pic];
map[@"key"] = [self objFor: self.key];
return map;
}
@end
Usage
//Parsing model
User *user = [[User alloc] init];
[user parseFromSnapshot: snapshot];
//Getting map for updateChildValues method
[user getMap]