2011-05-26 169 views
12

我想构建一个解析器/ objectMapper,它将为我从REST服务使用的JSON构建Objective C对象。从JSON到NSObject的对象映射库

我从RestKit中获得了一些灵感,让我的实体全部拥有一个“解码列表”,它告诉映射器哪些JSON键与哪些对象一起使用。像这样:

//ObjectEntity implementation 
+ (NSDictionary*) mapProperties { 

    /* 
    localPropertiy - JSONProperty 
    */ 

    return @{ 
      @"name": @"name", 
      @"category": @"category", 
      @"possible_scopes": @"possibleScopes", 
      @"possible_descriptions": @"possibleDescriptions", 
      @"key": @"keys"    
    }; 

} 

+ (NSDictionary*) mapRelations { 

    return [NSDictionary dictionary]; 
} 

我这样做是因为我喜欢将这些可更改值封装在它们引用的对象中。尽可能少地使Mapper知道。

映射器确实是这样的:

+ (NSArray*) parseData:(NSData*) jsonData intoObjectsOfType:(Class) objectClass { 

    //Parser result from web service 
    NSError *error = nil; 
    CJSONDeserializer *deserializer = [CJSONDeserializer deserializer]; 
    [deserializer setNullObject:nil]; 
    NSArray *objects = [deserializer deserializeAsArray:jsonData error:&error]; 

    NSMutableArray *result = [NSMutableArray array]; 

    for (NSDictionary *o in objects) { 

     id <EntityProtocol> entity = [[objectClass alloc] init]; 

     NSDictionary *jsonKeys = objectClass.mapProperties; 

     for (NSString *key in jsonKeys.allKeys) { 

      NSString *objectProperty = jsonKeys[key]; 
      NSString *value = o[key]; 
      if (value) 
       [entity setValue:value forKey:objectProperty]; 
     } 

     [result addObject:entity]; 

    } 

    return (NSArray*)result; 
} 

所以我消息解析器/映射器这样的:

NSArray *objects = [ObjectParser parseData:self.responseData intoObjectsOfType:ObjectEntity.class]; 

这意味着解析器必须知道我的根对象是什么,这是很好,因为从Web服务中检索它的对象当然有这方面的知识。

上面只适用于没有嵌套对象的JSON,我一直在试图构建解析器,以便它也将需要考虑的关系,建立所需的对象并将它们插入根对象,这需要递归和我一直跑到死胡同。

我想要一些帮助,以便我可以如何处理这个或任何洞察力,就好像这样的事情存在于图书馆中一样。也许是为了使用或者只是为了解决我遇到的问题。

预先感谢您。

+1

+1因为你正在做一些很酷的事情! – 2011-05-26 11:01:23

+0

谢谢亚历克,看起来很酷的事情现在很难弄清楚。 – RickiG 2011-05-30 21:38:21

+0

我们有一个相当复杂的发送/接收JSON的设置,但其要点是所有启用JSON的类都有一个'initWithDict'方法和一个'asDictionary'方法。每个类都负责读取/写入它拥有的数据,然后调用组件类的相同方法。 – 2014-01-29 18:04:44

回答

2

为什么不为类添加映射?

+ (NSDictionary *)mapClasses { 
    return @{ 
      @"category": [Category class], 
      // ... 
    }; 
} 

对于非容器的属性,你甚至可以做runtime introspection of properties避免多余的映射。

集装箱特性可以映射到特殊的包装对象:

[OMDynamicType typeWithBlock:^(id obj){ 
    if ([obj isKindOfClass:NSString.class] && [obj hasPrefix:@"foo"]) 
     return Foo.class; 
    else 
     return Bar.class; 
}], @"foo", 

实现这个可以去像:

+ (NSArray *)parseData:(NSData*)jsonData intoObjectsOfType:(Class)objectClass { 
    NSArray *parsed = /* ... */ 
    NSMutableArray *decoded = [NSMutableArray array]; 
    for (id obj in parsed) { 
     [decoded addObject:[self decodeRawObject:parsed intoObjectOfType:objectClass]]; 
    } 
    return decoded; 
} 

+ (id)decodeRawObject:(NSDictionary *)dict intoObjectOfType:(Class)objectClass { 
    // ... 
    NSDictionary *jsonKeys = objectClass.mapProperties; 
    NSDictionary *jsonClasses = objectClass.mapClasses; 

    for (NSString *key in jsonKeys.allKeys) { 
     NSString *objectProperty = jsonKeys[key]; 
     NSString *value = dict[key]; 
     if (value) { 
      id klass = jsonClasses[key]; 
      if (!klass) { 
       [entity setValue:value forKey:objectProperty]; 
      } else if (klass == klass.class) { 
       [entity setValue:[self decodeRawObject:value intoObjectOfType:klass] 
          forKey:objectProperty]; 
      } else if (/* check for containers and blocks */) { 
       // ... 
      } 
     } 
    } 
    // ... 
} 
+0

谢谢威尔伯!非常好的东西,足以让我去。我喜欢你在字典中返回一个类的想法,可以很容易地进行反思。 – RickiG 2011-05-30 21:37:27

10

的类型动态选择

[OMContainer arrayWithClass:Key.class], @"keys", 
[OMContainer dicionaryWithKeyClass:ScopeID.class valueClass:Scope.class], @"possibleScopes", 

甚至阻挡考虑使用RestKit:http://restkit.org

这个框架包含了你所需要的一切 - REST和JSON抽象,对象映射,甚至核心数据支持以及大量真正有用的东西,所有这些都以可定制和优雅的方式实现。

UPDATE:好的,在编写另一种映射方法时,我决定我不能再做了,并做了一个小框架。它反省了对象的属性,并通过一些调整为您提供免费的非常详细的描述,isEqual/hashCode,免费的NSCoding支持,并允许从JSON映射器(实际上,NSDictionary,但其他人会使用它)生成。所有NSNull检查,JSON中缺少字段,JSON中新的意外字段都会得到适当处理并正确报告。

如果有人希望这个共享给公众,你可以给我一些upvotes或评论。我最终会这样做,但我可能会考虑更快地分享它。

+0

现在一直使用它半年:)是强烈推荐! – RickiG 2012-01-25 11:24:37

+0

我发现它很难安装。在ASIHTTPRequests停止支持后,我更不愿意依赖更大的框架。 – zekel 2012-05-03 21:22:25

0

试用CSMapper,真是太神奇了!您只需创建与该类命名相同的plists,映射一些属性,然后使用一行代码轻松地将字典映射到您的对象。我已经在很多项目上测试过它,发现它非常干净,性能卓越。它使我能够灵活地轻松地在开发生命周期中响应API更改。

https://github.com/marcammann/CSMapper

我目前正在更新的文件和添加到在个人叉的项目,应该快了合并:)

https://github.com/AntonTheDev/CSMapper

1

我也尝试过同样的方法。我的主要问题是,准确地描述类型。例如,对于嵌套数组我用这样的:

@{ @"friends" : @[[Person class]] } 

但事情变得凌乱,因为我需要创造更多特殊的语法不同类型,我想支持。最后,我停止了追求这种方法并寻求另一种解决方案 - 也因为我需要做的运行时检查会减慢转换速度。

现在有很多解决方案。我建议看看Awesome iOS网站。我也想指出JSON Class Generator,因为它可以让你

  • 准确(对日期等特殊映射器在内)描述你的类型,
  • 类型的自动检查(不匹配报告和故障预置值提供),
  • 你不需要花时间写重复的代码(和生成的代码似乎总是“只是工作”)

我不想做广告,但是这个工具为我节约了很多工作,我投入到我的应用程序的其他功能秒。它出现了一点迟,现在世界似乎切换到Swift,但更有理由不浪费时间编写Objective-C模型。

下面是一些示例代码转换为/形式JSON:

// converting MyClass <-> NSDictionary 
MyClass  *myClass = [MyClass myClassWithDict:someJsonDictionary]; 
NSDictionary *jsonDict = [myClass toDict]; 

// converting NSDictionary <-> NSData 
NSDictionary *someJsonDictionary = [NSDictionary api_dictionaryWithJson:someJsonData]; 
NSData *jsonData = [someJsonDictionary api_toJson]; 

在之前的评论中提及的特征也被JSON Class Generator支持:

免费
-isEqual, -hash, -description, -copy, NSCoding/NSSecureCoding/NSKeyedArchiver 

支持,并检查缺失/ additional/NSNull /可选字段和JSON响应中的错误类型,报告错误,正常使用fallback值失败,并使用方法调度而不是运行时类型自省(这更快)执行此操作。