将NSData字节数组转换为字符串?

9

我有一个NSData对象。我需要将其字节转换为字符串并作为JSON发送。 description 返回十六进制,并且不可靠(根据多个SO帖子),因此我正在查看以下代码:

NSUInteger len = [imageData length];
Byte *byteData = (Byte*)malloc(len);
[imageData getBytes:&byteData length:len];

那么我该如何将byteData发送为JSON?我想要发送原始字节。

代码:

NSString *jsonBase64 = [imageData base64EncodedString];
NSLog(@"BASE 64 FINGERPRINT: %@", jsonBase64);
NSData *b64 = [NSData dataFromBase64String:jsonBase64];
NSLog(@"Equal: %d", [imageData isEqualToData:b64]);
NSLog(@"b64: %@", b64);
NSLog(@"original: %@", imageData);
NSString *decoded = [[NSString alloc] initWithData:b64 encoding:NSUTF8StringEncoding];
NSLog(@"decoded: %@", decoded);

我能够获取除最后一行decoded之外的所有数值。这表明原始字节没有使用NSUTF8编码格式进行格式化。


1
imageData是NSData对象,数据是否可表示为JSON字符串? - vadian
1
你想要字节以什么格式呈现?十六进制代码?Base-64 编码字符串?还是其他的? - rmaddy
1
一个用于 JSON 的字符串,没错。就是这个标题所说的。 - quantumpotato
2
https://dev59.com/mGw15IYBdhLWcg3wv-NM#6428558 - Lucas
1
1.) 经过base64编码后, 2.) 像老板一样发送。 - holex
显示剩余2条评论
4个回答

6

您是否尝试过使用类似这样的东西:

@implementation NSData (Base64)
- (NSString *)base64EncodedString
{
    return [self base64EncodedStringWithWrapWidth:0];
}

这将把你的NSData转换成base64字符串,另一端只需要对其进行解码即可。
编辑:@Lucas说你可以这样做:
NSString *myString = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];

但是我使用这种方法时遇到了一些问题,因为存在一些特殊字符,所以我开始使用base64字符串进行通信。

编辑3:尝试使用base64EncodedString方法。

    @implementation NSData (Base64)

    - (NSString *)base64EncodedString
    {
        return [self base64EncodedStringWithWrapWidth:0];
    }

    //Helper Method
    - (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
    {
        //ensure wrapWidth is a multiple of 4
        wrapWidth = (wrapWidth / 4) * 4;

        const char lookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

        long long inputLength = [self length];
        const unsigned char *inputBytes = [self bytes];

        long long maxOutputLength = (inputLength / 3 + 1) * 4;
        maxOutputLength += wrapWidth? (maxOutputLength / wrapWidth) * 2: 0;
        unsigned char *outputBytes = (unsigned char *)malloc((NSUInteger)maxOutputLength);

        long long i;
        long long outputLength = 0;
        for (i = 0; i < inputLength - 2; i += 3)
        {
            outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
            outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
            outputBytes[outputLength++] = lookup[((inputBytes[i + 1] & 0x0F) << 2) | ((inputBytes[i + 2] & 0xC0) >> 6)];
            outputBytes[outputLength++] = lookup[inputBytes[i + 2] & 0x3F];

            //add line break
            if (wrapWidth && (outputLength + 2) % (wrapWidth + 2) == 0)
            {
                outputBytes[outputLength++] = '\r';
                outputBytes[outputLength++] = '\n';
            }
        }

        //handle left-over data
        if (i == inputLength - 2)
        {
            // = terminator
            outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
            outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
            outputBytes[outputLength++] = lookup[(inputBytes[i + 1] & 0x0F) << 2];
            outputBytes[outputLength++] =   '=';
        }
        else if (i == inputLength - 1)
        {
            // == terminator
            outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
            outputBytes[outputLength++] = lookup[(inputBytes[i] & 0x03) << 4];
            outputBytes[outputLength++] = '=';
            outputBytes[outputLength++] = '=';
        }

        if (outputLength >= 4)
        {
            //truncate data to match actual output length
            outputBytes = realloc(outputBytes, (NSUInteger)outputLength);
            return [[NSString alloc] initWithBytesNoCopy:outputBytes
                                                  length:(NSUInteger)outputLength
                                                encoding:NSASCIIStringEncoding
                                            freeWhenDone:YES];
        }
        else if (outputBytes)
        {
            free(outputBytes);
        }
        return nil;
    }

1
我不需要Base64编码,我需要原始字节作为字符串。 - quantumpotato
1
就像我之前说过的,Base64更安全,因为有时特殊字符会导致从字节解码为字符串返回空值。 - rob180
1
但我绝对需要原始位。那么你能转换回NSString吗? - quantumpotato

6
  1. The reason the String is being considered 'unreliable' in previous Stack posts is because they too were attempting to use NSData objects where the ending bytes aren't properly terminated with NULL :

    NSString *jsonString = [NSString stringWithUTF8String:[nsDataObj bytes]];
    // This is unreliable because it may result in NULL string values
    
  2. Whereas the example below should give you your desired results because the NSData byte string will terminate correctly:

    NSString *jsonString = [[NSString alloc]  initWithBytes:[nsDataObj bytes] length:[nsDataObj length] encoding: NSUTF8StringEncoding];
    
你一开始的想法是正确的,希望这能帮助你解决目前的问题。祝你好运!
~ 编辑 ~
确保你从图像中这样声明你的NSData对象:
NSData *imageData = [[NSData alloc] init];
imageData = UIImagePNGRepresentation(yourImage);

1
我会试一试。 - quantumpotato
1
嘿@chrishaze,我从这里得到了一个空字符串:( - quantumpotato
1
你是否将图像转换为NSData对象?请查看我的最新编辑以获取详细信息。 - ChrisHaze
1
嘿,实际上我是直接从硬件API回调中获取的:-(void)fingerprintImageEvent:(NSData *)imageData imageType:(int)imageType numRows:(int)numRows numColumns:(int)numColumns { 我实际上可以通过执行UIImage *image = [[GRGrabba sharedGrabba].fingerprint getUIImageFromImageData:imageData imageType:imageType numRows:numRows numColumns:numColumns]; 来查看图像。但是API要求我将原始数据作为请求正文发送。所以我正在尝试从NSData *imageData获取它。 - quantumpotato
我并没有在Grabba注册成为开发者,但他们的Cordova插件中似乎内置了一个方法:convertImageToBase64。请检查一下iOS SDK是否有任何相关的方法? - ChrisHaze

3

NSData转换为NSString时,空终止符并不是唯一的问题。

NSString不适用于保存任意二进制数据。它需要一个编码格式。

如果你的NSData包含无效的UTF-8序列,那么初始化NSString将会失败

尽管文档在这一点上并不完全清晰,但对于initWithData,文档中写道:

如果初始化失败(例如数据不代表编码的有效数据),则返回nil。

另外: JSON规范将字符串定义为Unicode字符序列

这意味着即使你能够将原始数据转换成JSON字符串,在接收端进行UTF-8验证时,解析也可能会失败。

如果你不想使用Base64,可以查看此处的答案


3

本答案中的所有代码均为伪代码片段,您需要自己将算法转换为Objective-C或其他语言。

您的问题引发了许多问题...... 您从以下内容开始:

我有一个NSData对象。 我需要将其字节转换为字符串并发送为JSON。 描述返回十六进制并且不可靠(根据各种SO帖子)。

这似乎意味着您希望将字节作为字符串编码,以便准备在另一端解码它们回到字节。 如果是这种情况,您有许多选择,例如Base-64编码等。 如果您想要简单的东西,您可以将每个字节都编码为其两个字符的十六进制值,伪代码概述:

NSMutableString *encodedString = @"".mutableCopy;
foreach aByte in byteData
   [encodedString appendFormat:@"%02x", aByte];

格式%02x表示两个十六进制数字,左边补零。这将生成一个字符串,可以发送为JSON并在另一端轻松解码。通过网络传输的字节大小可能是UTF-8编码的两倍,因为UTF-8是JSON推荐的编码方式。
然而,在回答中,您写道:“但我绝对需要原始位。”您的意思是什么?您的接收方会将其获取到的JSON字符串解释为原始字节序列吗?如果是这样,您需要解决多个问题。JSON字符串是JavaScript字符串的子集,并以UCS-2或UTF-16格式存储,即它们是16位值的序列而不是8位值。如果您将每个字节编码为字符串中的字符,则使用16位表示,如果接收器可以访问字节流,则必须跳过每隔一个字节。当然,如果您的接收器逐个字符访问字符串,则每个16位字符可以被截断回8位字节。现在,您可能认为如果采用此方法,则每个8位字节都可以作为字符串的一部分输出为字符,但那行不通。虽然所有值1-255都是有效的Unicode字符代码点,并且JavaScript/JSON允许字符串中有NULs(0值),但并非所有这些值都是可打印的,您不能在字符串中放置双引号"而不对其进行转义,转义字符是\ -所有这些都需要编码到字符串中。您最终会得到类似以下的内容:
NSMutableString *encodedString = @"".mutableCopy;
foreach aByte in byteData
   if (isprint(aByte) && aByte != '"' && aByte != '\\')
       [encodedString appendFormat:@"%c", aByte];
   otherwise
       [encodedString appendFormat:@"\\u00%02x", aByte]; // JSON unicode escape sequence

这将生成一个字符串,当被JSON解码器解析时,每个字节都会给您一个字符(16位),其中高8位为零。然而,如果将此字符串传递给JSON编码器,则会对已经编码的Unicode转义序列进行编码...因此,为了避免这种情况,您真的需要自己将此字符串发送到网络中...

感到困惑了吗?变得复杂了吗?那么为什么要将二进制字节数据作为字符串发送?您从未说明您的高级目标是什么,或者是否已知字节数据的任何信息(例如它是否代表某种编码中的字符)

如果这只是一组字节,则为什么不将其作为数字的JSON数组发送-一个字节只是在0-255范围内的数字。要做到这一点,您可以使用以下代码:

NSMutableArray *encodedBytes = [NSMutableArray new];
foreach aByte in byteData
   [encodedBytes addObject:@(aByte)]; // add aByte as an NSNumber object

现在将encodedBytes传递给NSJSONSerialisation,它会将数字的JSON数组发送到网络上,接收器会反转这个过程,将每个字节重新打包到一个字节缓冲区中,你就可以获得你的字节了。这种方法避免了所有有效字符串、编码和转义问题。希望对你有所帮助。

我正在尝试发送二进制数据,因为这是API所要求的。它作为请求正文发送。 - quantumpotato
哪个API需要二进制数据?如果接收方的API需要二进制数据,并且您正在尝试通过将数据作为JSON传递来调用它,则只需在通信过程中对二进制数据进行某种形式的编码即可。您不需要将其编码为字符串,但是您可以这样做。第一种解决方案,将其编码为十六进制或base-64字符串,或者最后一种解决方案,将其编码为数字数组,都可以满足您的需求。如果数据很大,则可能希望选择base-64作为最简单、最紧凑的方法,否则任何方法都可以。希望对您有所帮助。 - CRD
我注意到你说“作为请求正文发送”,这表明你正在尝试与Web服务通信,并且有一些关于它期望的规范,而不是与调用某些API的自己的服务器端代码进行通信。如果是这种情况,您需要在问题中提供有关API及其期望的更多详细信息 - 没有API应该要求在JSON中使用二进制数据而不指定如何编码它 - JSON没有二进制数据作为基本数据类型。 - CRD

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