将字节数组转换为JSON格式,反之亦然。

93

能否将byte[](字节数组)放入JSON中?

如果可以,在Java中我该如何做?然后读取该JSON并将该字段再次转换为byte[]


13
JSON 不支持那个。使用 Base64。 - SLaks
1
它确实可以。我使用了这个:jsonObj.put(byte[]); - Amin Sh
3
这是你的库支持它,不是 JSON 本身。字节数组不会以字节数组的形式存储在 JSON 中,JSON 是一种文本格式,用于人类阅读。你的库可能将字节数组解释为 UTF-8 编码的字符串并显示出来,也可能显示二进制字符串、Base64 编码或十六进制字符串,谁知道呢。 - Zabuzard
6个回答

89

这是base64编码字节数组的良好示例。当您添加Unicode字符以发送像PDF文档之类的内容时,情况会变得更加复杂。编码字节数组后,编码后的字符串可用作JSON属性值。

Apache Commons提供了很好的实用工具:

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

https://developer.mozilla.org/zh-CN/docs/Web/API/WindowBase64/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[]。你有什么线索吗? - Yusril Maulidan Raji
这个答案有一定道理(即通常的方式是Base64编码),但并不切题。问题是关于字节的;这些字节是否为PDF并不重要,也不需要使用任何字符集来生成这些字节。 "服务器端"、一些 getUnsecureContent 方法和 JavaScript 也不相关。(感觉问题后来被编辑了,即使没有标记,这种情况可能吗?如果是这样,我很抱歉。) - EndlosSchleife
1
@sam-nunnally 如果我们将其编码为UTF-8字符串,这里会有任何问题吗? - Ayyappa

15

在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解析器/生成器库进行此转换。


5

根据@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。

然而,空间方面通常会损失一些。使用UTF-8每个非ASCII字节将占用2个字节,而BAIS每运行3n+r个这样的字节就使用(2+4n+r?(r+1):0)个字节(其中r是余数)。在UTF-16上会胜利,但谁会在JSON中使用呢?

(这种编码技巧适用于任何语言——8859-1得到了广泛支持。)


3
被严重低估的答案。 - undefined
2
被严重低估的答案。 - Robino
+1 是因为它既聪明又适用于不同的语言。我曾经有一个 C# 项目,我需要将一个二进制 blob 作为字符串传递给一个 API。(虽然我对此并不满意,但这不是我想要争论的问题。)使用这种编码将文件读取为字符串的方式非常完美。 - undefined

2
令人惊讶的是,现在的org.json允许你直接将byte[]对象放入json中,并且它仍然可读。你甚至可以通过websocket发送结果对象,在另一端也能读取。但我还不确定结果对象的大小是比转换为base64的字节数组更大还是更小,如果更小的话,那就太好了。
似乎很难在Java中测量这样一个json对象占用多少空间。如果你的json只包含字符串,那么通过简单地将其转换为字符串就可以轻松实现,但如果其中包含bytearray,则恐怕就不那么简单了。
在Java中将我们的json转换为字符串会用一个10个字符长的字符串替换我的bytearray,看起来像一个id。在node.js中做同样的事情会用一个未加引号的值代替我们的byte[],该值读作<Buffered Array: f0 ff ff ...>,后者的长度表明大小增加了约300%,正如预期的那样。

无论您的数据格式如何,都可以将其转换为字节数组,然后使用json.put("some name", bytearray)发送它,使用org.json稍后可以将其读取为byte[] readarray=(byte[])json.get("some name")。 - quealegriamasalegre
这并不是特别新的内容,也不仅限于 byte[]。org.json将数组类型视为JSON数组,并序列化其元素的值。byte被视为数字,因此生成的JSON只是一个数字的JSON数组。 - Savior
@ Saviour,你真的尝试过吗?我感觉我们并不是在谈论同一件事情。而且你所说的根本不是真的,一个bytearray绝对可以包含括号或者撇号的字节表示,这也正是为什么base64存在的原因:因此你可以得到一个字符串表示形式的bytearrays(无论是图片、音频文件或其他任何东西),它不包含用于json格式的冒号、括号和其他特殊字符。 - quealegriamasalegre
一个字节数组完全可以包含括号或撇号的字节表示,这将会破坏 JSON。为什么会这样呢?任何表示方式只是数组中的一个字节,需要在下游进行解码。但这不应该影响 JSON 对象本身,它纯粹只包含字节(一个数字)。 - undefined
1
@Anna 是的,我现在明白了“救世主”是什么意思。起初我没有意识到他指的是将字节(0-255)用字符表示的编码方式,我希望org.json能够做得更高效一些(像魔法一样XD)。现在我明白了。总的来说,如果我关心效率的话,应该使用Protobuffs。对于通过json发送数据,base64(也许还有一些base128的实现)可能是我们能得到的最好的选择。 - undefined
显示剩余10条评论

1
如果您的字节数组中可能包含ASCII字符序列并且希望能够看到它们,那么您可能更喜欢BAIS(字节数组字符串)格式而不是Base64。BAIS的好处在于,如果所有字节恰好都是ASCII,则它们将被1对1地转换为字符串(例如,字节数组{65,66,67}变为简单的"ABC")。此外,BAIS通常比Base64给出更小的文件大小(但这并不保证)。
将字节数组转换为BAIS字符串后,像处理其他字符串一样将其写入JSON。
下面是一个Java类(从原始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;
          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#单元测试

1
Base64 编码的不易读性真是让人烦恼。不错! - Robino
1
Base64的人类可读性不足真是令人烦恼。太好了! - undefined

-7

这样怎么样:

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

5
使用String(byte[])构造函数会将其编码为一个字符串,应用默认编码。这可能会改变原始字节内容。此外,在另一端,您应该清楚地知道使用的编码方式。 - fcracker79

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