【问题标题】:Why does SBJson JSON parsing only get the last key of interest?为什么 SBJson JSON 解析只得到最后一个感兴趣的键?
【发布时间】:2012-05-16 23:46:10
【问题描述】:

我正在使用以下 JSON:http://www.kb.dk/tekst/mobil/aabningstider_en.json 当我尝试通过键“位置”解析它时:

//    get response in the form of a utf-8 encoded json string
NSString *jsonString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];

//    get most parent node from json string
NSDictionary *json = [jsonString JSONValue];

//    get key-path from jason up to the point of json object
NSDictionary *locations = [json objectForKey:@"location"];

NSLog( @"%@", locations );

//    iterate through all of the location objects in the json
for (NSDictionary *loc in locations )
{

    //        pull library name from the json object
    NSString *name = [loc valueForKey:@"name"];
    //        add library data table arrays respectively
    [ libraryNames addObject: ( ( name == nil | name.length > 0 ) ? name : @"UnNamed" ) ];

}

当我通过 NSLog 打印对象位置时:

{
address = "Universitetsparken 4, 3. etage, 2100 K\U00f8benhavn \U00d8";
desc = "";
lastUpdated = "";
latlng = "55.703124,12.559596";
link = "http://www.farma.ku.dk/index.php?id=3742";
name = "Faculty of Pharmaceutical Sciences Library";
parts =     {
    part =         {
        hour =             {
            day = "5.June Constitution Day (Denmark)";
            open = Closed;
        };
        hours =             {
            hour =                 {
                day = Friday;
                open = "10-16";
            };
        };
        name = main;
    };
};
}

这只是“位置”键的最后一个值。难道我做错了什么? 我尝试通过http://jsonlint.com/ 验证 JSON,但是当我输入上面的 JSON URL 时,它说“有效” - 仍然只显示最后一个“位置”键”,但是如果我复制粘贴它,它不会验证 JSON,必须通过从字符串中删除换行来修复。

此外,当我尝试解析 JSON 并获取“名称”字段时,出现以下异常:

2012-05-08 15:37:04.941 iPhone App Tabbed[563:f803] *** Terminating app due to uncaught         exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x68bfe70> valueForUndefinedKey:]:     this class is not key value coding-compliant for the key name.'
*** First throw call stack:
(0x13dc052 0x156dd0a 0x13dbf11 0x9d2f0e 0x941841 0x940ca9 0x4593 0xf964e 0x114b89 0x1149bd     0x112f8a 0x112e2f 0x1148f4 0x13ddec9 0x365c2 0x3655a 0x25b569 0x13ddec9 0x365c2 0x3655a     0xdbb76 0xdc03f 0xdbbab 0x25dd1f 0x13ddec9 0x365c2 0x3655a 0xdbb76 0xdc03f 0xdb2fe 0x5ba30     0x5bc56 0x42384 0x35aa9 0x12c6fa9 0x13b01c5 0x1315022 0x131390a 0x1312db4 0x1312ccb 0x12c5879     0x12c593e 0x33a9b 0x281d 0x2785)
terminate called throwing an exception(lldb) 

如果“locations”标签是一个用方括号 ([]) 括起来的数组对象会更有意义,但是现在它只是一个普通键值对的序列......遗憾的是,这就是我拥有的 JSON一起工作。

请提供帮助,非常感谢! :)

真诚地, 彼得。

【问题讨论】:

    标签: iphone objective-c ios json sbjson


    【解决方案1】:

    您必须使用的 JSON 可能是有效的,但它没有多大意义。它有一本大字典,其中location 键重复了很多次。大多数 JSON 解析器将简单地返回重复键的最后一个值。如果您可以更改结构以使用数组来代替,那将是最好的,但如果您不能,仍然有希望。您可以读取流并将来自location 键的值填充到数组中,因为它们从其中出来。你会这样做:

    @interface BadJsonHelper : NSObject
    @property(strong) NSMutableArray *accumulator;
    @end    
    @implementation BadJsonHelper
    - (void)parser:(SBJsonStreamParser *)parser foundArray:(NSArray *)array {
        // void
    }
    - (void)parser:(SBJsonStreamParser *)parser foundObject:(NSDictionary *)dict {
        [accumulator addObject:dict];
    }
    @end
    

    您可以将那个小助手类放在文件顶部,在您正在工作的类的@implementation 部分之外。 (@interface 和 @implementation 不需要在不同的文件中。)

    在您的代码中,您可以这样使用它:

    BadJsonHelper *helper = [[BadJsonHelper alloc] init];
    helper.accumulator = [NSMutableArray array];
    
    SBJsonStreamParserAdapter *adapter = [[SBJsonStreamParserAdapter new] init];
    adapter.delegate = helper;
    adapter.levelsToSkip = 1;
    
    SBJsonStreamParser *parser = [[SBJsonStreamParser alloc] init];
    parser.delegate = adapter;
    
    switch ([parser parse: responseData]) {
        case SBJsonStreamParserComplete: 
            NSLog(@"%@", helper.accumulator);
            break;
        case SBJsonStreamParserWaitingForData:
            NSLog(@"Didn't get all the JSON yet...");
            break;
        case SBJsonStreamParserError:
            NSLog(@"Error: %@", parser.error);
            break;
    }
    

    此示例最初改编自以下测试: https://github.com/stig/json-framework/blob/master/Tests/StreamParserIntegrationTest.m

    更新:我创建了一个功能齐全的示例项目,它异步加载 JSON 并对其进行解析。可从github 获得。

    【讨论】:

      【解决方案2】:

      JSON 是有效的,但是关于项目数组的定义存在一个基本问题。

      JSON 不是使用方括号定义 locations 的数组,而是一遍又一遍地重新定义相同的 location 键/值对。换句话说,JSON 最初说 location 的值是名为“The Black Diamond”的集合,但在它立即用名为“Faculty Library of Humanities”的集合重新定义它,依此类推,直到最后一个位置 Faculty of Pharmaceutical Sciences Library ”。

      partshours 也是如此。

      如果您无法修复 JSON 的结果并且您确实需要使其正常工作,您可能需要修改 JSON,删除“位置”键并正确添加括号。

      编辑

      您也可以使用 NSScanner 并手动处理 JSON 结果。有点 hacky,但只要 JSON 格式没有显着变化,它就可以工作。

      编辑

      这段代码应该可以完成工作......

      NSString *jsonString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
      
      int indx = 1;
      for (;;)
      {
          NSRange locationRange = [jsonString rangeOfString:@"\"location\":"];
      
          if (locationRange.location == NSNotFound) break;
      
          jsonString = [jsonString stringByReplacingCharactersInRange:locationRange
                                                           withString:[NSString stringWithFormat:@"\"location%d\":", indx++]];
      } 
      

      【讨论】:

      • 酷,谢谢 tcamin。我想了想,但我希望有一种侵入性较小的方法来做到这一点。真的没有别的办法了吗?
      • 不是特别优雅,但如果您想尽可能地保持这种结构,您可能需要将位置键重命名为“location1”、“location2”、“location3”。在应用程序上,您将迭代 location-N 直到 objectForKey 为 nil
      • 这是一个很好的建议,如何将键“location”的第 n 次出现替换为“location-n”,其中 n 是它的出现次数?谢谢! :)
      • 检查我的第二个编辑希望这会有所帮助!
      • 这可能有效,但它是一个非常老套的解决方案。请参阅my answer 了解如何在不借助黑客的情况下执行此操作。 (免责声明:我是 SBJson 的作者。)
      【解决方案3】:
      NSDictionary *locations = [json objectForKey:@"location"];
      

      如您所见,SBJson 解析 JSON 的结果是NSDictionary。字典包含键/值对,键是对的唯一标识符。

      您需要处理的 JSON 数据是有效的,但不是一个好的数据。每RFC 4627 - 2.2

      对象结构表示为一对围绕零个或多个名称/值对(或成员)的花括号。名称是一个字符串。每个名称后面都有一个冒号,将名称与值分开。单个逗号将值与后面的名称分开。 对象中的名称应该是唯一的。

      jQuery之类的东西也可以解析JSON,但是结果和SBJson一样(最后一个为第一个)。见Do JSON keys need to be unique?

      这不是必须的,但仍然不是一个好习惯。如果您能够在服务器端(甚至在接收到它之后在客户端)更改 JSON 数据的结构,而不是按原样解析它,那会容易得多。

      【讨论】:

      • 嗨,谢谢哈雷。不幸的是,我应该尽可能地保持 JSON 的结构,尽管它可能很糟糕。它由我的项目所有者提供,因此更改它并不理想。有什么想法吗?
      • 如果您想按原样手动解析 JSON,我同意 tcamin 关于使用 NSScanner 的观点。另一种方法是在 SBJson 解析之前通过字符串操作修复 JSON。
      猜你喜欢
      • 2020-02-10
      • 1970-01-01
      • 2015-10-14
      • 1970-01-01
      • 2019-12-10
      • 1970-01-01
      • 2011-08-10
      • 1970-01-01
      相关资源
      最近更新 更多