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

有没有可能把 byte[](字节数组)放到 JSON

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

281244 次浏览

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

Java 为 Base64编码和解码 byte[]提供了不同的方法,DatatypeConverter就是其中之一。

很简单

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

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

下面是 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;
}

简单来说:

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

如果您的字节数组可能包含您希望能够看到的 ASCII 字符的运行,那么您可能更喜欢 BAIS (Byte Array In String)格式,而不是 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;
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 # 单元测试

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

似乎很难测量这样一个 json 对象在 Java 中占用了多少空间。如果你的 json 仅仅由字符串组成,那么只要简单地将它串起来就可以很容易地实现,但是在它里面有一个字节数组,我担心它不是那么简单。

在 java 中将 json 字符串化,取代了 bytearray,得到了一个看起来像 id 的10个字符的字符串。在 node.js 中做同样的事情,将我们的 byte[]替换为读取 <Buffered Array: f0 ff ff ...>的未引用值,后者的长度表明大小增加了约300% ,正如预期的那样

与@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 + 4 N + R?(R + 1) : 0)每运行一次3N + R这样的字节(r 是余数)。