你如何使用NSJSONSerialization反序列化一个带转义字符的JSON字符串?

15

我有一个iOS应用程序需要处理来自Web服务的响应。响应是一个包含序列化JSON对象的序列化JSON字符串,类似于这样:

"{ \"name\" : \"Bob\", \"age\" : 21 }"

请注意,这个响应是一个JSON字符串,而不是一个JSON对象。我需要做的是将该字符串反序列化,以便得到如下结果:

{ "name" : "Bob", "age" : 21 }

然后我可以使用+[NSJSONSerialization JSONObjectWithData:options:error:]将其反序列化为NSDictionary

但是,首先我该如何做呢?也就是说,我该如何“取消转义”字符串,以便获得序列化的JSON对象?如果顶级对象是一个数组或字典,+[NSJSONSerialization JSONObjectWithData:options:error:]才适用;它不能处理字符串。

我最终编写了自己的JSON字符串解析器,希望符合RFC 4627第2.5节。但我怀疑我可能忽略了一些使用NSJSONSerialization或其他可用方法的简单方法。


只需切掉前导和尾随引号,然后将所有的 \" 替换为 " - user529758
\uXXXX 转义序列使得简单的查找和替换变得困难。 - Kristopher Johnson
编写一个简单的扫描器来查找转义字符并替换它们应该不难。 - user529758
1
这就是我所做的:https://gist.github.com/kristopherjohnson/5715018 - Kristopher Johnson
它运行良好。但正如我在问题中所说,我怀疑有一种更简单的方法来使用现有的API。 - Kristopher Johnson
显示剩余4条评论
4个回答

24
如果您有嵌套的JSON,则只需调用两次JSONObjectWithData
NSString *string =  @"\"{ \\\"name\\\" : \\\"Bob\\\", \\\"age\\\" : 21 }\"";
// --> the string
// "{ \"name\" : \"Bob\", \"age\" : 21 }"

NSError *error;
NSString *outerJson = [NSJSONSerialization JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding]
                              options:NSJSONReadingAllowFragments error:&error];
// --> the string
//  { "name" : "Bob", "age" : 21 }
NSDictionary *innerJson = [NSJSONSerialization JSONObjectWithData:[outerJson dataUsingEncoding:NSUTF8StringEncoding]
                              options:0 error:&error];
// --> the dictionary
// { age = 21; name = Bob; }

当我尝试这样做时,出现了错误信息。似乎只有在顶层对象是数组或字典的情况下才能正常工作。 - Kristopher Johnson
我发誓之前我尝试过'NSJSONReadingAllowFragments',但它没有起作用,但现在它有效了。感谢让我再次尝试它。 - Kristopher Johnson
@CouchDeveloper:我不太理解你的评论。"{\"name\":\"Bob\",\"age\":21}"是字符串{"name":"Bob","age":21}的正确JSON编码形式,而后者恰好是字典的JSON编码形式。我有什么疏忽吗? - Martin R
如果给定的字符串确实是JSON的有效字符串,那么你是正确的!我只是不确定给定的字符串是否实际上是以这种方式编码的,以及为什么:如果您将一个键/值对放入JSON表示中 - 其值是JSON,则在它经过传输后,您将检索到完全相同的JSON。也就是说,如果您打印NSLog("JSON: %@", dict[@"json"]) - 它将打印:__{ "name" : "Bob", "age" : 21 }__ - CouchDeveloper
再次尝试澄清:如果您要通过JSON解析器/解码器发送任何字符串 - 并且JSON是一个字符串 - 那么您永远不会自己进行编码。相反,您创建一个“JSON表示”(分层对象结构)并将其传递给JSON解析器/解码器。在接收方,您将运行解析器并获取一些其他平台的JSON表示 - 比如基础架构层次结构。当检索作为JSON初始化的字符串时,在接收方,您会获得完全相同的字符串 - 排除字符编码。 - CouchDeveloper
显示剩余2条评论

0

将字符串转换为数据:

NSString *string = @"{ \"name\" : \"Bob\", \"age\" : 21 }";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];

当我调用NSJSONSerialization时,我确实拥有一个NSData对象。因此,就好像你的代码是string = @"\"{ \\"name\\" : \\"Bob\\", \\"age\\" : 21 }\""; - Kristopher Johnson
你是怎么获取那个字符串的?听起来在某个地方它被双重转义了,这就是你需要调试的问题所在。 - Joe Hankin
2
网络服务对其进行了双重转义。你和我可能都认为它不应该这样做,但它确实这样做了,而我必须处理它。 - Kristopher Johnson

0

只需剪掉前导和尾随引号,然后将所有的 \" 替换为 ":

NSString *sub = [original substringWithRange:(NSRange){ 1, original.length - 2 }];
NSString *unescaped = [sub stringByReplacingOccurrencesOfString:@"\\\" withString:@"\"];

那是我的初步hacky解决方案,但它只适用于字符串中没有其他特殊字符(\n\t\u1234等)。但我不能假设这一点。 - Kristopher Johnson
一个JSON字符串需要转义这些字符。因此,当应用编码器时,它只会转义转义符:“(\ \ n)”。 - CouchDeveloper

-1

首先,我们应该问一下,为什么服务器不直接将JSON作为子结构包含在内呢?

但无论如何,你得到的字符串似乎是一个转义的JSON。这实际上意味着什么,完全取决于Web服务开发人员。我怀疑只有双引号和转义本身已经用转义符\进行了转义。生成的字符串不是“序列化”的-JSON已经被序列化了-而是编码。为了将其还原回来-您需要再次“取消转义”或解码它:

一个小的C++片段展示了如何做到这一点(我知道你要求Objective-C-但这太容易了):

编辑:该代码也适用于UTF-16和UTF-32-以及任何字节顺序-如果编码器只是机械地执行我怀疑的操作,则它也适用于转义的Unicode字符,例如\u1234等。

编辑-不,它不适用于UTF-16和UTF-32。样例必须修复(这很容易)。但请确保您使用的是UTF-8-这几乎总是情况。

#include <iostream>

char input[] = u8R"___({ \"name\" : \"Bob\", \"age\" : 21 })___";

// Unescapes the character sequence "in-situ".
// Returns a pointer to "past-the-end" of the unescaped string.
static char* unescape(char* first, char* last) {
    char* dest = first;
    while (first != last) {
        if (*first == '\\') {
            ++first;
        }
        *dest++ = *first++;
    }
    return dest;
}

int main(int argc, const char * argv[])
{
    char* first = input;
    char* last = first + strlen(input);
    std::string s(input, unescape(first, last));

    std::cout << s << std::endl;

    return 0;
}

输出:

{ "name" : "Bob", "age" : 21 }


1
你的示例操作输入 { \"name\" : \"Bob\", \"age\" : 21 }。但实际服务器响应是(据我理解)"{ \"name\" : \"Bob\", \"age\" : 21 }"(请注意前导和尾随引号)。这就是为什么 NSJSONReadingAllowFragments 起作用的原因。 - Martin R
好的,这意味着NSJSONSerialization将其解释为JSON字符串,然后返回此顶级对象 - 这是一个NSString。因此,它有效地将“JSON字符串解码器”应用于给定的字符串。这可能有效,但我仍然会澄清Web服务如何解码给定的字符串。 - CouchDeveloper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接