从JSON到NSObjects的对象映射库

时间:2011-05-26 10:22:13

标签: objective-c cocoa-touch ios json

我正在尝试构建一个解析器/ objectMapper,它将为我从REST服务中使用的JSON构建Objective C对象。

我从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,我一直在尝试构建解析器,以便将关系考虑在内,构建所需的对象并将它们插入到根对象中,这需要递归而且我一直陷入死胡同。

我想帮助我如何处理这个或任何见解,好像这样的东西作为一个库存在。也许是为了使用或者只是为了解决我遇到问题的部分。

提前谢谢。

5 个答案:

答案 0 :(得分:10)

考虑使用RestKit:http://restkit.org

这个框架拥有您所需要的一切 - REST和JSON抽象,对象映射,甚至Core Data支持以及许多非常有用的东西,所有这些都以可定制和优雅的方式实现。

更新:好的,在编写另一种映射方法时,我决定不再这样做了,并做了一个小框架。它内省了对象的属性,并通过一些调整为您提供了免费的漂亮描述,isEqual / hashCode,免费的NSCoding支持,并允许生成/从JSON映射器生成(确实,实际上,NSDictionary,但谁会将其用于其他东西)。所有NSNull检查,JSON中缺少的字段,JSON中的新意外字段都可以正常处理并正确报告。

如果有人希望将此内容分享给公众,您可以给我一些赞成或评论。我最终会这样做,但我可能会考虑更快地分享它。

答案 1 :(得分:2)

为什么不为类添加映射?

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

对于非容器属性,您甚至可以执行runtime introspection of properties以避免冗余映射。

容器属性可以映射到特殊的包装器对象:

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

甚至用于动态选择类型的块:

[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 */) {
                // ...
            }
        }
    }
    // ...
}

答案 2 :(得分:1)

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

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

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

现在有很多解决方案。我建议您查看Awesome iOS网站。我还想指出JSON Class Generator,因为它允许你

  • 准确描述您的类型(包括日期等特殊地图制作者),
  • 自动检查类型(报告的不匹配和提供的回退值),
  • 你不需要花时间编写重复的代码(并且生成的代码似乎总是“只是工作”)

我讨厌做广告,但是这个工具为我节省了很多工作,我投入了我的应用程序的其他功能。它出现的有点晚了,现在世界似乎转向了Swift,但更有理由不花时间在Objective-C上编写模型。

以下是一些转换为/ form 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

支持免费并检查JSON响应中缺少/附加​​/ NSNull /可选字段和错误类型,报告错误,使用回退值正常失败并使用方法调度而不是运行时类型内省(这更快)

答案 3 :(得分:0)

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

https://github.com/marcammann/CSMapper

我目前正在更新文档并在个人分支上添加项目,希望很快就会合并:)

https://github.com/AntonTheDev/CSMapper

答案 4 :(得分:0)

我的建议是在NSObject上使用Motis类别。它重量轻,可以对您的对象类型执行自动验证,并且使用起来非常简单。

在另一个问题中有一个问题:Mapping JSON objects in custom objects

希望它有用。