将字节数组放入 JSON,反之亦然

Posted

技术标签:

【中文标题】将字节数组放入 JSON,反之亦然【英文标题】:Put byte array to JSON and vice versa 【发布时间】:2014-01-09 12:03:21 【问题描述】:

是否可以将byte[](字节数组)放入JSON

如果是这样,我该如何在 java 中做到这一点?然后读取该 JSON 并将该字段再次转换为 byte[]?

【问题讨论】:

JSON 不支持。使用 Base64。 确实如此。我用这个:jsonObj.put(byte[]); 那是你的库支持它,而不是 JSON 本身。字节数组不会存储为 JSON 中的字节数组,JSON 是一种人类可读的文本格式。您的库可能会将字节数组解释为 UTF-8 编码的字符串并显示它,或者可能显示二进制字符串,可能是 base64,也可能是十六进制字符串,谁知道呢。 【参考方案1】:

根据@Qwertie 的建议,但在惰性方面更进一步,您可以只是假装每个字节都是一个 ISO-8859-1 字符。对于初学者来说,ISO-8859-1 是一种单字节编码,匹配 Unicode 的前 256 个代码点。

所以@Ash 的答案实际上可以用字符集来兑换:

byte[] args2 = getByteArry();
String byteStr = new String(args2, Charset.forName("ISO-8859-1"));

这种编码具有与 BAIS 相同的可读性,其优势在于它的处理速度比 BAIS 或 base64 更快,因为需要的分支更少。看起来 JSON 解析器做得更多,但这很好,因为通过转义或 UTF-8 处理非 ASCII 无论如何都是 JSON 解析器工作的一部分。它可以更好地映射到某些格式,例如带有配置文件的 MessagePack。

然而,在空间方面,这通常是一种损失,因为没有人会为 JSON 使用 UTF-16。对于 UTF-8,每个非 ASCII 字节将占用 2 个字节,而 BAIS 使用 (2+4n + r?(r+1 ):0) 字节,每次运行 3n+r 个这样的字节(r 是余数)。

【讨论】:

【参考方案2】:

现在令人惊讶的是,org.json 现在允许您将 byte[] 对象直接放入 json 中,并且它仍然可读。您甚至可以通过 websocket 发送生成的对象,并且它可以在另一端读取。但我还不确定结果对象的大小是否大于或小于将字节数组转换为 base64 时的大小,如果它更小肯定会很整洁。

似乎很难衡量这样一个 json 对象在 java 中占用了多少空间。如果您的 json 仅由字符串组成,则可以通过简单地对其进行字符串化来轻松实现,但里面有一个字节数组,我担心它并不那么简单。

在 java 中对我们的 json 进行字符串化将我的 bytearray 替换为一个 10 个字符的字符串,看起来像一个 id。在 node.js 中执行相同操作会将我们的 byte[] 替换为读取 <Buffered Array: f0 ff ff ...> 的未引用值,后者的长度表示预期的大小增加了 ~300%

【讨论】:

所以如果字节数组只有整数值或字符串值,那么? 我的意思是,如果你的 bytearray 只包含字符串或整数,我想直接在 json 中发送字符串和整数会更容易。 hmm 所以如果有某种字节数据我从让我们说一台机器只处理顺序内存访问,每个地址从某个偏移量开始,每个索引都有一些位(变量)那么你的意思是这个数据可以直接转储到一个带有一些结构定义的json,代表一个顺序数组?例如在 0x122 地址有 2 位数据,然后立即从地址 0x125 开始有大约 4 位数据 无论您的数据格式如何,您都可以将其简单地转换为字节数组,然后使用 json.put("some name", bytearray) 并发送它,然后使用 org.json 您可以稍后将其读取为 byte[] readarray=(byte[])json.get("some name") 这不是特别新的,也不是特定于byte[]。 org.json 将数组类型视为相同:作为 JSON 数组并序列化其元素的值。 byte 被视为数字,因此生成的 JSON 只是数字的 JSON 数组。【参考方案3】:

如果您的字节数组可能包含您希望能够看到的一系列 ASCII 字符,您可能更喜欢 BAIS(字符串中的字节数组)格式而不是 Base64。 BAIS 的好处在于,如果所有字节恰好都是 ASCII,它们会一对一地转换为字符串(例如,字节数组 65,66,67 变成简单的 "ABC")此外,BAIS 通常会为您提供更小的文件大小比 Base64(不能保证)。

将字节数组转换为 BAIS 字符串后,将其写入 JSON,就像写入任何其他字符串一样。

这是一个 Java 类(从original C# 移植),它将字节数组转换为字符串并返回。

import java.io.*;
import java.lang.*;
import java.util.*;

public class ByteArrayInString

  // Encodes a byte array to a string with BAIS encoding, which 
  // preserves runs of ASCII characters unchanged.
  //
  // For simplicity, this method's base-64 encoding always encodes groups of 
  // three bytes if possible (as four characters). This decision may 
  // unfortunately cut off the beginning of some ASCII runs.
  public static String convert(byte[] bytes)  return convert(bytes, true); 
  public static String convert(byte[] bytes, boolean allowControlChars)
  
    StringBuilder sb = new StringBuilder();
    int i = 0;
    int b;
    while (i < bytes.length)
    
      b = get(bytes,i++);
      if (isAscii(b, allowControlChars))
        sb.append((char)b);
      else 
        sb.append('\b');
        // Do binary encoding in groups of 3 bytes
        for (;; b = get(bytes,i++)) 
          int accum = b;
          System.out.println("i="+i);
          if (i < bytes.length) 
            b = get(bytes,i++);
            accum = (accum << 8) | b;
            if (i < bytes.length) 
              b = get(bytes,i++);
              accum = (accum << 8) | b;
              sb.append(encodeBase64Digit(accum >> 18));
              sb.append(encodeBase64Digit(accum >> 12));
              sb.append(encodeBase64Digit(accum >> 6));
              sb.append(encodeBase64Digit(accum));
              if (i >= bytes.length)
                break;
             else 
              sb.append(encodeBase64Digit(accum >> 10));
              sb.append(encodeBase64Digit(accum >> 4));
              sb.append(encodeBase64Digit(accum << 2));
              break;
            
           else 
            sb.append(encodeBase64Digit(accum >> 2));
            sb.append(encodeBase64Digit(accum << 4));
            break;
          
          if (isAscii(get(bytes,i), allowControlChars) &&
            (i+1 >= bytes.length || isAscii(get(bytes,i), allowControlChars)) &&
            (i+2 >= bytes.length || isAscii(get(bytes,i), allowControlChars))) 
            sb.append('!'); // return to ASCII mode
            break;
          
        
      
    
    return sb.toString();
  

  // Decodes a BAIS string back to a byte array.
  public static byte[] convert(String s)
  
    byte[] b;
    try 
      b = s.getBytes("UTF8");
     catch(UnsupportedEncodingException e)  
      throw new RuntimeException(e.getMessage());
    
    for (int i = 0; i < b.length - 1; ++i) 
      if (b[i] == '\b') 
        int iOut = i++;

        for (;;) 
          int cur;
          if (i >= b.length || ((cur = get(b, i)) < 63 || cur > 126))
            throw new RuntimeException("String cannot be interpreted as a BAIS array");
          int digit = (cur - 64) & 63;
          int zeros = 16 - 6; // number of 0 bits on right side of accum
          int accum = digit << zeros;

          while (++i < b.length)
          
            if ((cur = get(b, i)) < 63 || cur > 126)
              break;
            digit = (cur - 64) & 63;
            zeros -= 6;
            accum |= digit << zeros;
            if (zeros <= 8)
            
              b[iOut++] = (byte)(accum >> 8);
              accum <<= 8;
              zeros += 8;
            
          

          if ((accum & 0xFF00) != 0 || (i < b.length && b[i] != '!'))
            throw new RuntimeException("String cannot be interpreted as BAIS array");
          i++;

          // Start taking bytes verbatim
          while (i < b.length && b[i] != '\b')
            b[iOut++] = b[i++];
          if (i >= b.length)
            return Arrays.copyOfRange(b, 0, iOut);
          i++;
        
      
    
    return b;
  

  static int get(byte[] bytes, int i)  return ((int)bytes[i]) & 0xFF; 

  public static int decodeBase64Digit(char digit)
     return digit >= 63 && digit <= 126 ? (digit - 64) & 63 : -1; 
  public static char encodeBase64Digit(int digit)
     return (char)((digit + 1 & 63) + 63); 
  static boolean isAscii(int b, boolean allowControlChars)
     return b < 127 && (b >= 32 || (allowControlChars && b != '\b')); 

另请参阅:C# unit tests。

【讨论】:

【参考方案4】:

简单地说:

byte[] args2 = getByteArry();
String byteStr = new String(args2);

【讨论】:

String(byte[]) 构造函数会将其编码为应用默认编码的字符串。这可能会改变原始字节内容。此外,另一方面,您也应该清楚您使用的是哪种编码。【参考方案5】:

这是 base64 编码字节数组的一个很好的例子。当您在混合中添加 unicode 字符以发送 PDF 文档之类的内容时,情况会变得更加复杂。对字节数组进行编码后,编码后的字符串可以用作 JSON 属性值。

Apache commons 提供了很好的实用程序:

 byte[] bytes = getByteArr();
 String base64String = Base64.encodeBase64String(bytes);
 byte[] backToBytes = Base64.decodeBase64(base64String);

https://developer.mozilla.org/en-US/docs/Web/javascript/Base64_encoding_and_decoding

Java 服务器端示例:

public String getUnsecureContentBase64(String url)
        throws ClientProtocolException, IOException 

            //getUnsecureContent will generate some byte[]
    byte[] result = getUnsecureContent(url);

            // use apache org.apache.commons.codec.binary.Base64
            // if you're sending back as a http request result you may have to
            // org.apache.commons.httpclient.util.URIUtil.encodeQuery
    return Base64.encodeBase64String(result);

JavaScript 解码:

//decode URL encoding if encoded before returning result
var uriEncodedString = decodeURIComponent(response);

var byteArr = base64DecToArr(uriEncodedString);

//from mozilla
function b64ToUint6 (nChr) 

  return nChr > 64 && nChr < 91 ?
      nChr - 65
    : nChr > 96 && nChr < 123 ?
      nChr - 71
    : nChr > 47 && nChr < 58 ?
      nChr + 4
    : nChr === 43 ?
      62
    : nChr === 47 ?
      63
    :
      0;



function base64DecToArr (sBase64, nBlocksSize) 

  var
    sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
    nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen);

  for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) 
    nMod4 = nInIdx & 3;
    nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
    if (nMod4 === 3 || nInLen - nInIdx === 1) 
      for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) 
        taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
      
      nUint24 = 0;

    
  

  return taBytes;

【讨论】:

不知何故,我在编译时收到了这些错误error: cannot find symbol method encodeBase64String(byte[])error: incompatible types: String cannot be converted to byte[]。你有什么线索吗? 这个答案有一些道理(即通常的方法是 Base64 编码),但这不是重点。问题是关于字节的;这些是否是PDF都没有关系。既不是您可能用来生成字节的字符集。 “服务器端”、一些getUnsecureContent 方法和JavaScript 也不是主题。 (几乎感觉这个问题后来被编辑了,即使没有这样标记也可能吗?如果是这样,我很抱歉。) @sam-nunnally 如果我们编码为 UTF-8 字符串有什么问题吗?【参考方案6】:

在 json 中发送二进制文件的典型方法是对其进行 base64 编码。

Java 提供了不同的方式来对 byte[] 进行 Base64 编码和解码。其中之一是DatatypeConverter

很简单

byte[] originalBytes = new byte[]  1, 2, 3, 4, 5;
String base64Encoded = DatatypeConverter.printBase64Binary(originalBytes);
byte[] base64Decoded = DatatypeConverter.parseBase64Binary(base64Encoded);

您必须根据您使用的 json 解析器/生成器库进行此转换。

【讨论】:

以上是关于将字节数组放入 JSON,反之亦然的主要内容,如果未能解决你的问题,请参考以下文章

C#字节数组到字符串数组[重复]

如何将 SAFEARRAY(字节数组)放入 HTML 隐藏字段

将 VARIANT 转换为字节,反之亦然? [复制]

C ++将文件的所有字节放入char数组中?

浮点数组到字节数组和反之亦然

将表示 UCHAR 数组的 json 字符串响应转换为字节数组