Eu tenho uma comunicação webSocket, eu recebo uma string codificada em base64, converto em uint8 e trabalho nisso, mas agora eu preciso enviar de volta, eu tenho o array uint8 e preciso convertê-lo em string base64, então posso enviar. Como posso fazer essa conversão?
javascript
arrays
base64
Caio Keto
fonte
fonte
Respostas:
Todas as soluções já propostas apresentam problemas graves. Algumas soluções não funcionam em matrizes grandes, algumas fornecem saída incorreta, algumas geram um erro na chamada de btoa se uma string intermediária contiver caracteres multibyte, algumas consomem mais memória do que o necessário.
Então, implementei uma função de conversão direta que funciona independentemente da entrada. Ele converte cerca de 5 milhões de bytes por segundo na minha máquina.
https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
Exibir trecho de código
/* MIT License Copyright (c) 2020 Egor Nepomnyaschih Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* // This constant can also be computed with the following algorithm: const base64abc = [], A = "A".charCodeAt(0), a = "a".charCodeAt(0), n = "0".charCodeAt(0); for (let i = 0; i < 26; ++i) { base64abc.push(String.fromCharCode(A + i)); } for (let i = 0; i < 26; ++i) { base64abc.push(String.fromCharCode(a + i)); } for (let i = 0; i < 10; ++i) { base64abc.push(String.fromCharCode(n + i)); } base64abc.push("+"); base64abc.push("/"); */ const base64abc = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/" ]; /* // This constant can also be computed with the following algorithm: const l = 256, base64codes = new Uint8Array(l); for (let i = 0; i < l; ++i) { base64codes[i] = 255; // invalid character } base64abc.forEach((char, index) => { base64codes[char.charCodeAt(0)] = index; }); base64codes["=".charCodeAt(0)] = 0; // ignored anyway, so we just need to prevent an error */ const base64codes = [ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 ]; function getBase64Code(charCode) { if (charCode >= base64codes.length) { throw new Error("Unable to parse base64 string."); } const code = base64codes[charCode]; if (code === 255) { throw new Error("Unable to parse base64 string."); } return code; } export function bytesToBase64(bytes) { let result = '', i, l = bytes.length; for (i = 2; i < l; i += 3) { result += base64abc[bytes[i - 2] >> 2]; result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)]; result += base64abc[((bytes[i - 1] & 0x0F) << 2) | (bytes[i] >> 6)]; result += base64abc[bytes[i] & 0x3F]; } if (i === l + 1) { // 1 octet yet to write result += base64abc[bytes[i - 2] >> 2]; result += base64abc[(bytes[i - 2] & 0x03) << 4]; result += "=="; } if (i === l) { // 2 octets yet to write result += base64abc[bytes[i - 2] >> 2]; result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)]; result += base64abc[(bytes[i - 1] & 0x0F) << 2]; result += "="; } return result; } export function base64ToBytes(str) { if (str.length % 4 !== 0) { throw new Error("Unable to parse base64 string."); } const index = str.indexOf("="); if (index !== -1 && index < str.length - 2) { throw new Error("Unable to parse base64 string."); } let missingOctets = str.endsWith("==") ? 2 : str.endsWith("=") ? 1 : 0, n = str.length, result = new Uint8Array(3 * (n / 4)), buffer; for (let i = 0, j = 0; i < n; i += 4, j += 3) { buffer = getBase64Code(str.charCodeAt(i)) << 18 | getBase64Code(str.charCodeAt(i + 1)) << 12 | getBase64Code(str.charCodeAt(i + 2)) << 6 | getBase64Code(str.charCodeAt(i + 3)); result[j] = buffer >> 16; result[j + 1] = (buffer >> 8) & 0xFF; result[j + 2] = buffer & 0xFF; } return result.subarray(0, result.length - missingOctets); } export function base64encode(str, encoder = new TextEncoder()) { return bytesToBase64(encoder.encode(str)); } export function base64decode(str, decoder = new TextDecoder()) { return decoder.decode(base64ToBytes(str)); }
fonte
"ABCDEFG..."
?Se seus dados podem conter sequências de bytes múltiplos (não uma sequência ASCII simples) e seu navegador tem TextDecoder , então você deve usar isso para decodificar seus dados (especifique a codificação necessária para o TextDecoder):
var u8 = new Uint8Array([65, 66, 67, 68]); var decoder = new TextDecoder('utf8'); var b64encoded = btoa(decoder.decode(u8));
Se você precisa oferecer suporte a navegadores que não têm TextDecoder (atualmente apenas IE e Edge), a melhor opção é usar um polyfill TextDecoder .
Se seus dados contiverem ASCII simples (não Unicode / UTF-8 multibyte), há uma alternativa simples de uso
String.fromCharCode
que deve ter suporte universalmente:var ascii = new Uint8Array([65, 66, 67, 68]); var b64encoded = btoa(String.fromCharCode.apply(null, ascii));
E para decodificar a string base64 de volta para um Uint8Array:
var u8_2 = new Uint8Array(atob(b64encoded).split("").map(function(c) { return c.charCodeAt(0); }));
Se você tiver buffers de array muito grandes, a aplicação pode falhar e você pode precisar dividir o buffer (com base no postado por @RohitSengar). Novamente, observe que isso está correto apenas se o seu buffer contiver apenas caracteres ASCII não multibyte:
function Uint8ToString(u8a){ var CHUNK_SZ = 0x8000; var c = []; for (var i=0; i < u8a.length; i+=CHUNK_SZ) { c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ))); } return c.join(""); } // Usage var u8 = new Uint8Array([65, 66, 67, 68]); var b64encoded = btoa(Uint8ToString(u8));
fonte
btoa(String.fromCharCode.apply(null, myArray))
Uint8Array
.TextDecoder
é absolutamente errado usar aqui, porque se o seuUint8Array
tiver bytes no intervalo 128..255, o decodificador de texto irá convertê-los erroneamente em caracteres Unicode, o que quebrará o conversor base64.Solução e teste muito simples para JavaScript!
ToBase64 = function (u8) { return btoa(String.fromCharCode.apply(null, u8)); } FromBase64 = function (str) { return atob(str).split('').map(function (c) { return c.charCodeAt(0); }); } var u8 = new Uint8Array(256); for (var i = 0; i < 256; i++) u8[i] = i; var b64 = ToBase64(u8); console.debug(b64); console.debug(FromBase64(b64));
fonte
RangeError: Maximum call stack size exceeded
Se você estiver usando Node.js, você pode usar este código para converter Uint8Array em base64
var b64 = Buffer.from(u8).toString('base64');
fonte
function Uint8ToBase64(u8Arr){ var CHUNK_SIZE = 0x8000; //arbitrary number var index = 0; var length = u8Arr.length; var result = ''; var slice; while (index < length) { slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length)); result += String.fromCharCode.apply(null, slice); index += CHUNK_SIZE; } return btoa(result); }
Você pode usar esta função se tiver um Uint8Array muito grande. Isso é para Javascript, pode ser útil no caso de FileReader readAsArrayBuffer.
fonte
String.fromCharCode.apply()
métodos @Jens não podem reproduzir UTF-8: caracteres UTF-8 podem variar em comprimento de um byte a quatro bytes, masString.fromCharCode.apply()
examina um UInt8Array em segmentos de UInt8, portanto, assume erroneamente que cada caractere tem exatamente um byte de comprimento e é independente do vizinho uns. Se todos os caracteres codificados na entrada UInt8Array estiverem no intervalo ASCII (byte único), funcionará por acaso, mas não pode reproduzir UTF-8 completo. Você precisa de TextDecoder ou um algoritmo semelhante para isso.Aqui está uma função JS para isso:
function urlBase64ToUint8Array(base64String) { var padding = '='.repeat((4 - base64String.length % 4) % 4); var base64 = (base64String + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); var rawData = window.atob(base64); var outputArray = new Uint8Array(rawData.length); for (var i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; }
fonte
JS puro - sem meio-passo de string (sem btoa)
Na solução abaixo, omito a conversão para string. IDEA é o seguinte:
=
ou==
ao resultadoA solução abaixo funciona em blocos de 3 bytes, portanto é boa para grandes arrays. Uma solução semelhante para converter base64 em matriz binária (sem
atob
) está AQUIExibir trecho de código
function bytesArrToBase64(arr) { const abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // base64 alphabet const bin = n => n.toString(2).padStart(8,0); // convert num to 8-bit binary string const l = arr.length let result = ''; for(let i=0; i<=(l-1)/3; i++) { let c1 = i*3+1>=l; // case when "=" is on end let c2 = i*3+2>=l; // case when "=" is on end let chunk = bin(arr[3*i]) + bin(c1? 0:arr[3*i+1]) + bin(c2? 0:arr[3*i+2]); let r = chunk.match(/.{1,6}/g).map((x,j)=> j==3&&c2 ? '=' :(j==2&&c1 ? '=':abc[+('0b'+x)])); result += r.join(''); } return result; } // ---------- // TEST // ---------- let test = "Alice's Adventure in Wondeland."; let testBytes = [...test].map(c=> c.charCodeAt(0) ); console.log('test string:', test); console.log('bytes:', JSON.stringify(testBytes)); console.log('btoa ', btoa(test)); console.log('bytesArrToBase64', bytesArrToBase64(testBytes));
fonte
Use o seguinte para converter a matriz uint8 em uma string codificada em base64
function arrayBufferToBase64(buffer) { var binary = ''; var bytes = [].slice.call(new Uint8Array(buffer)); bytes.forEach((b) => binary += String.fromCharCode(b)); return window.btoa(binary); };
fonte
Veja aqui https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding#Apencha.3A_Decode_a_Base64_string_to_Uint8Array_or_ArrayBuffer
(Decodifique uma string Base64 para Uint8Array ou ArrayBuffer com suporte Unicode)
fonte
Se tudo o que você deseja é uma implementação JS de um codificador base64, para poder enviar dados de volta, experimente a
btoa
função.Algumas notas rápidas sobre btoa - não é padrão, então os navegadores não são forçados a suportá-lo. No entanto, a maioria dos navegadores sim. Os grandes, pelo menos.
atob
é a conversão oposta.Se você precisar de uma implementação diferente ou encontrar um caso extremo em que o navegador não tenha ideia do que você está falando, procurar um codificador base64 para JS não seria muito difícil.
Acho que há 3 deles circulando no site da minha empresa, por algum motivo ...
fonte
npm install google-closure-library --save
require("google-closure-library"); goog.require('goog.crypt.base64'); var result =goog.crypt.base64.encodeByteArray(Uint8Array.of(1,83,27,99,102,66)); console.log(result);
$node index.js
escreveria AVMbY2Y = no console.fonte
-ve
resposta votada seja aceita ao invés de uma altamente+ve
.