PropertyMapping
在进行数据的映射之前,我们需要定义Object(本文中所有Object都表示一个复杂的Object对象)中每一个property的映射关系PropertyMapping,其中ObjectMapping是标示当前property是否是一个Object,如果是nil,则表示当前property就是简单的映射。如果不是nil,则需要将property进行展开,把当前的property当成另外一个Object进行映射。
#import
@class ObjectMapping;
@interface PropertyMapping : NSObject
@property (nonatomic, weak) ObjectMapping *objectMapping;
@property (nonatomic, copy) NSString *sourceKeyPath;
@property (nonatomic, copy) NSString *destinationKeyPath;
+ (PropertyMapping *)propertyMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath withMapping:(ObjectMapping *)mapping;
@end
#import "PropertyMapping.h"
@implementation PropertyMapping
+ (PropertyMapping *)propertyMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath withMapping:(ObjectMapping *)mapping
{
PropertyMapping *propertyMapping = [self new];
propertyMapping.sourceKeyPath = sourceKeyPath;
propertyMapping.destinationKeyPath = destinationKeyPath;
propertyMapping.objectMapping = mapping;
return propertyMapping;
}
@end
ObjectMapping
ObjectMapping管理整个Object的PropertyMapping,包括objectClass,propertyMappings。并且提供Object反映射的inverseMapping。
#import
@class PropertyMapping;
@interface ObjectMapping : NSObject
- (ObjectMapping *) initWithClass:(Class) objectClass;
- (ObjectMapping *) inverseMapping;
- (void) addPropertyMapping:(PropertyMapping *) porpertyMapping;
//add simple propertys
- (void) addPropertyMappingsFromDictionary:(NSDictionary *) dic;
@end
#import "ObjectMapping.h"
@interface ObjectMapping ()
@property (nonatomic, readwrite) Class objectClass;
@property (nonatomic, readwrite) NSMutableArray *propertyMappings;
@property (nonatomic, readwrite) NSMutableArray *mappedKeyPaths;
@end
@implementation ObjectMapping
+ (instancetype)mappingForClass:(Class)objectClass
{
return [[self alloc] initWithClass:objectClass];
}
- (id)initWithClass:(Class)objectClass
{
self = [super init];
if (self)
{
self.objectClass = objectClass;
self.propertyMappings = [NSMutableArray new];
self.mappedKeyPaths = [NSMutableArray new];
}
return self;
}
- (void)addPropertyMapping:(PropertyMapping *)porpertyMapping
{
NSAssert([self.mappedKeyPaths containsObject:porpertyMapping.destinationKeyPath] == NO,
@"Unable to add mapping for keyPath %@, one already exists...", porpertyMapping.destinationKeyPath);
NSAssert(self.propertyMappings, @"mutableRelationshipMappings is nil");
[self.mappedKeyPaths addObject:porpertyMapping.destinationKeyPath];
[self.propertyMappings addObject:porpertyMapping];
}
- (void)addPropertyMappingsFromDictionary:(NSDictionary *)dic
{
for (NSString *attributeKeyPath in dic)
{
[self addPropertyMapping:[PropertyMapping propertyMappingFromKeyPath:attributeKeyPath toKeyPath:[dic objectForKey:attributeKeyPath] withMapping:nil]];
}
}
- (ObjectMapping *)inverseMapping
{
ObjectMapping *inverseMapping = nil;
inverseMapping = [ObjectMapping mappingForClass:[NSMutableDictionary class]];
for (PropertyMapping *propertyMapping in self.propertyMappings)
{
ObjectMapping *inverseRelationshipMapping = nil;
if(propertyMapping.objectMapping && [propertyMapping.objectMapping isKindOfClass:[UPObjectMapping class]])//relationship mapping
{
inverseRelationshipMapping = [propertyMapping.objectMapping inverseMapping];
}
[inverseMapping addPropertyMapping:[PropertyMapping propertyMappingFromKeyPath:propertyMapping.destinationKeyPath toKeyPath:propertyMapping.sourceKeyPath withMapping:inverseRelationshipMapping]];
}
return inverseMapping;
}
@end
EntityMapping
EntityMapping从ObjectMapping继承,实现从JSONData到CoreData的映射。#import
#import "ObjectMapping.h"
@class ObjectMapping;
@interface EntityMapping : ObjectMapping
@property (nonatomic, strong) NSEntityDescription *entity;
- (id)initWithEntity:(NSEntityDescription *)entity;
+ (instancetype)mappingForEntityForName:(NSString *)entityName inManagedObjectStore:(NSPersistentStore *)store;
@end
#import "EntityMapping.h"
@implementation EntityMapping
- (id)initWithEntity:(NSEntityDescription *)entity
{
NSParameterAssert(entity);
Class objectClass = NSClassFromString([entity managedObjectClassName]);
NSParameterAssert(objectClass);
self = [self initWithClass:objectClass];
if (self) {
self.entity = entity;
}
return self;
}
+ (instancetype)mappingForEntityForName:(NSString *)entityName inManagedObjectStore:(NSPersistentStore *)store
{
NSParameterAssert(entityName);
NSEntityDescription *entity = [[store.persistentStoreCoordinator.managedObjectModel entitiesByName] objectForKey:entityName];
NSAssert(entity, @"Unable to find an Entity with the name '%@' in the managed object model", entityName);
return [[self alloc] initWithEntity:entity];
}
@end
#import
#import "ObjectMapping.h"
@interface MappingOperation : NSObject
+ (id) mapTargetObjectFromJSONData:(id)jsonData withMapping:(ObjectMapping *)mapping;
+ (id) mapJSONDataFromTargetObject:(id)object withMapping:(ObjectMapping *)mapping;
@end
#import "MappingOperation.h"
@implementation MappingOperation
+ (id) mapTargetObjectFromJSONData:(id) jsonData withMapping:(ObjectMapping *) mapping
{
NSAssert(jsonData != nil, @"Cannot perform a mapping operation without a sourceObject object");
NSAssert(mapping != nil, @"Cannot perform a mapping operation without a mapping");
NSError *error = nil;
id representation = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (error)
{
NSLog(@"%@", [error description]);
return nil;
}
id mappingResult = [self mapTargetForRepresentation:representation withMapping:mapping];
return mappingResult;
}
+ (id) mapJSONDataFromTargetObject:(id) object withMapping:(ObjectMapping *) mapping
{
NSAssert(object != nil, @"Cannot perform a mapping operation without a sourceObject object");
NSAssert(mapping != nil, @"Cannot perform a mapping operation without a mapping");
id inverseResult = [self mapJSONDatafromRepresentation:object withMapping:mapping];
NSError *error = nil;
id jsonData = [NSJSONSerialization dataWithJSONObject:inverseResult options:NSJSONWritingPrettyPrinted error:&error];
if (error)
{
NSLog(@"%@", [error description]);
return nil;
}
return jsonData;
}
#pragma private method
+ (id) mapTargetForRepresentation:(id) representation withMapping:(ObjectMapping *) mapping
{
if(!representation)
{
NSLog(@"Mapping data of %@ is nil.",mapping.objectClass);
return nil;
}
//handle representation if it is NSArray or NSSet
if([representation isKindOfClass:[NSArray class]] || [representation isKindOfClass:[NSSet class]])
{
//NSLog(@"%i", [representation count]);
NSMutableSet *set = [[NSMutableSet alloc]init];
for(NSDictionary *dic in representation)
{
[set addObject:[self mapTargetForRepresentation:dic withMapping:mapping]];
}
return set;
}
NSObject *result = nil;
if (![mapping isKindOfClass:[EntityMapping class]])
{
result = [[mapping.objectClass alloc]init];
}else
{
EntityMapping *entityMapping = (EntityMapping *)mapping;
NSEntityDescription *entity = [entityMapping entity];
NSString *keyAttribute = [mapping.objectClass keyAttribute];
if(keyAttribute && ![keyAttribute isEqualToString:@""])
{
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass(mapping.objectClass)];
for(UPPropertyMapping *map in mapping.propertyMappings)
{
if([map.destinationKeyPath isEqualToString:keyAttribute])
{
request.predicate = [NSPredicate predicateWithFormat:@"%K=%@",keyAttribute,[representation valueForKey:map.sourceKeyPath]];
break;
}
}
NSError *error = nil;
NSArray *exists = [[NSManagedObjectContext contextForCurrentThread] executeFetchRequest:request error:&error];
if ([exists count] > 0)
{
//
result = [exists objectAtIndex:0];
//NSLog(@"object exists:%@", [result valueForKey:keyAttribute]);
}else
{
result = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:[NSManagedObjectContext contextForCurrentThread]];
}
}else
{
result = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:[NSManagedObjectContext contextForCurrentThread]];
}
}
for(UPPropertyMapping *map in mapping.propertyMappings)
{
id value = [self getValueFromRepresentation:representation forKeyPath:map.sourceKeyPath];
if (map.objectMapping)
{
value = [self mapTargetForRepresentation:value withMapping:map.objectMapping];
}
if ([value isKindOfClass:[NSNull class]])
{
value = nil;
}
//[result setValue:value forKey:map.destinationKeyPath];
[self setObject:result withValue:value forKey:map.destinationKeyPath];
//NSLog(@"%@ = %@",map.destinationKeyPath,value);
}
return result;
}
+ (void) setObject:(id) object withValue:(id) value forKey:(NSString *) keyPath
{
NSRange contain = [keyPath rangeOfString:@"."];
//check if the keyPath contains ".", if contain ,then break the keyPath into prefix and suffix
if (contain.length > 0)
{
NSString *prefix = [keyPath substringToIndex:contain.location];
NSString *suffix = [keyPath substringFromIndex:contain.location + 1];
if([[object allKeys] containsObject:prefix])//check if the object has the property of prefix
{
// use the already exist property
id property = [object valueForKey:prefix];
[self setObject:property withValue:value forKey:suffix];
}else
{
//add a property to the object, just handle simple object, can't handle managedobject.
//so when design object mapping, can't add mapping like this @"class1.property":@"class2.property" when class2 is kind of managedobject
id property = [[NSMutableDictionary alloc]init];
[self setObject:property withValue:value forKey:suffix];
[object setValue:property forKey:prefix];
}
}else
{
//handle original property
[object setValue:value forKey:keyPath];
}
}
+ (id) getValueFromRepresentation:(id) representation forKeyPath:(NSString *) keyPath
{
NSRange contain = [keyPath rangeOfString:@"."];
//check if the keyPath contains ".", if contain ,then break the keyPath into prefix and suffix
if (contain.length > 0)
{
NSString *prefix = [keyPath substringToIndex:contain.location];
NSString *suffix = [keyPath substringFromIndex:contain.location + 1];
return [self getValueFromRepresentation:[representation valueForKey:prefix] forKeyPath:suffix];
}
//return original property value
return [representation valueForKey:keyPath];
}
+ (id) mapJSONDatafromRepresentation:(id) representation withMapping:(ObjectMapping *) mapping
{
if(!representation)
{
NSLog(@"Mapping data of %@ is nil.",mapping.objectClass);
return nil;
}
//handle representation if it is NSArray or NSSet
if([representation isKindOfClass:[NSArray class]] || [representation isKindOfClass:[NSSet class]])
{
//NSLog(@"%i", [representation count]);
NSMutableArray *array = [[NSMutableArray alloc]init];
for(NSDictionary *dic in representation)
{
[array addObject:[self mapJSONDatafromRepresentation:dic withMapping:mapping]];
}
return array;
}
NSObject *result = [[mapping.objectClass alloc]init];
for(UPPropertyMapping *map in mapping.propertyMappings)
{
id value = nil;
if (map.objectMapping)
{
value = [self mapJSONDatafromRepresentation:[representation valueForKey:map.sourceKeyPath] withMapping:map.objectMapping];
}else
{
value = [representation valueForKey:map.sourceKeyPath];
}
[self setObject:result withValue:value forKey:map.destinationKeyPath];
}
return result;
}
@end