从 JSON 到 NSObjects 的对象映射库

Posted

技术标签:

【中文标题】从 JSON 到 NSObjects 的对象映射库【英文标题】:Object Mapping library from JSON to NSObjects 【发布时间】:2011-05-26 10:22:13 【问题描述】:

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

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

提前谢谢你。

【问题讨论】:

+1 因为你做的很酷! 谢谢亚历克,这些天似乎很难弄清楚很酷的事情。 我们有一个相当复杂的设置来发送/接收 JSON,但它的要点是所有启用 JSON 的类都有一个 initWithDict 方法和一个 asDictionary 方法。每个类负责读取/写入它“拥有”的数据,并依次为组件类调用相同的方法。 【参考方案1】:

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

这个框架拥有你所需要的一切——REST 和 JSON 抽象、对象映射,甚至是核心数据支持和许多真正有用的东西,所有这些都以可定制和优雅的方式实现。

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

如果有人希望将此分享给公众,您可以给我点赞或 cmets。我最终会这样做,但我可能会考虑更快地分享它。

【讨论】:

已经用了半年了:)是的,强烈推荐! 我发现它很难安装。而在 ASIHTTPRequests 停止支持后,我更不愿意依赖更大的框架。【参考方案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 */) 
                // ...
            
        
    
    // ...

【讨论】:

谢谢威尔伯!非常好的东西,足以让我前进。我喜欢你在字典中返回一个类的想法,这样可以很容易地进行自省。【参考方案3】:

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

@ @"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

免费支持,并检查 JSON 响应中的缺失/附加/NSNull/可选字段和错误类型,报告错误,优雅地失败并返回后备值,并使用方法分派而不是运行时类型自省来执行此操作(更快) .

【讨论】:

【参考方案4】:

试试 CSMapper,太棒了!您只需创建与类命名相同的 plist,映射一些属性,然后您可以使用一行代码轻松地将字典映射到您的对象。我已经在许多项目上对其进行了测试,发现它非常干净、高效。它使我能够灵活地在开发生命周期中轻松响应 API 更改。

https://github.com/marcammann/CSMapper

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

https://github.com/AntonTheDev/CSMapper

【讨论】:

【参考方案5】:

我的建议是在 NSObject 上使用 Motis 类别。它是轻量级的,对您的对象类型执行自动验证并且非常易于使用。

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

希望有用。

【讨论】:

以上是关于从 JSON 到 NSObjects 的对象映射库的主要内容,如果未能解决你的问题,请参考以下文章

iPhone json 库将 json 映射到我自己的对象

Firebase Documentsnapshot 映射到 Flutter 中的 Json 对象

对包含 NSNumber 变量的 NSobjects 的 NSArray 进行排序

从 json 动态创建的 Javascript 表单输入,需要将对象映射到另一个对象的正确字段并保存

jOOQ & PostgreSQL:将从复杂 jsonb 中提取的嵌套 json 对象映射到自定义类型

将 JSON 对象映射到 TypeScript 对象