Obtuve una comunicación webSocket, recibo una cadena codificada en base64, la convierto a uint8 y trabajo en ella, pero ahora necesito enviarla de vuelta, obtuve la matriz uint8 y necesito convertirla a una cadena base64, para poder enviarla. ¿Cómo puedo hacer esta conversión?
javascript
arrays
base64
Caio Keto
fuente
fuente
Respuestas:
Todas las soluciones ya propuestas tienen graves problemas. Algunas soluciones no funcionan en matrices grandes, algunas proporcionan una salida incorrecta, algunas arrojan un error en la llamada btoa si una cadena intermedia contiene caracteres de varios bytes, algunas consumen más memoria de la necesaria.
Así que implementé una función de conversión directa que simplemente funciona independientemente de la entrada. Convierte alrededor de 5 millones de bytes por segundo en mi máquina.
https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
Mostrar fragmento 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)); }
fuente
"ABCDEFG..."
?Si sus datos pueden contener secuencias de varios bytes (no una secuencia ASCII simple) y su navegador tiene TextDecoder , entonces debe usar eso para decodificar sus datos (especifique la codificación requerida para TextDecoder):
var u8 = new Uint8Array([65, 66, 67, 68]); var decoder = new TextDecoder('utf8'); var b64encoded = btoa(decoder.decode(u8));
Si necesita admitir navegadores que no tienen TextDecoder (actualmente solo IE y Edge), entonces la mejor opción es usar un polyfill TextDecoder .
Si sus datos contienen ASCII simple (no Unicode / UTF-8 multibyte), existe una alternativa simple
String.fromCharCode
que debería ser compatible de forma bastante universal:var ascii = new Uint8Array([65, 66, 67, 68]); var b64encoded = btoa(String.fromCharCode.apply(null, ascii));
Y para decodificar la cadena base64 de nuevo a un Uint8Array:
var u8_2 = new Uint8Array(atob(b64encoded).split("").map(function(c) { return c.charCodeAt(0); }));
Si tiene búferes de matriz muy grandes, la aplicación puede fallar y es posible que deba fragmentar el búfer (según el publicado por @RohitSengar). Nuevamente, tenga en cuenta que esto solo es correcto si su búfer solo contiene caracteres ASCII que no son 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));
fuente
btoa(String.fromCharCode.apply(null, myArray))
Uint8Array
.TextDecoder
es absolutamente incorrecto usar aquí, porque siUint8Array
tiene bytes en el rango 128..255, el decodificador de texto los convertirá erróneamente en caracteres Unicode, lo que romperá el convertidor base64.¡Solución muy simple y prueba 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));
fuente
RangeError: Maximum call stack size exceeded
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); }
Puede utilizar esta función si tiene un Uint8Array muy grande. Esto es para Javascript, puede ser útil en el caso de FileReader readAsArrayBuffer.
fuente
String.fromCharCode.apply()
métodos @Jens no pueden reproducir UTF-8: los caracteres UTF-8 pueden variar en longitud de un byte a cuatro bytes, sin embargo,String.fromCharCode.apply()
examina un UInt8Array en segmentos de UInt8, por lo que asume erróneamente que cada carácter tiene exactamente un byte de largo e independiente del vecino unos. Si todos los caracteres codificados en la entrada UInt8Array están en el rango ASCII (de un solo byte), funcionará por casualidad, pero no puede reproducir UTF-8 completo. Necesita TextDecoder o un algoritmo similar para eso.Si está utilizando Node.js, puede usar este código para convertir Uint8Array a base64
var b64 = Buffer.from(u8).toString('base64');
fuente
Aquí hay una función JS para esto:
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; }
fuente
Pure JS - sin cadena middlestep (sin btoa)
En la siguiente solución, omito la conversión a cadena. IDEA está siguiendo:
=
o==
al resultadoLa siguiente solución funciona en fragmentos de 3 bytes, por lo que es buena para matrices grandes. Una solución similar para convertir base64 a una matriz binaria (sin
atob
) está AQUÍMostrar fragmento 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));
fuente
Utilice lo siguiente para convertir la matriz uint8 en una cadena codificada en base64
function arrayBufferToBase64(buffer) { var binary = ''; var bytes = [].slice.call(new Uint8Array(buffer)); bytes.forEach((b) => binary += String.fromCharCode(b)); return window.btoa(binary); };
fuente
Vea aquí https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding#Appendix.3A_Decode_a_Base64_string_to_Uint8Array_or_ArrayBuffer
(Decodifique una cadena Base64 a Uint8Array o ArrayBuffer con soporte Unicode)
fuente
En el sitio web de Mozilla Developer Network se muestra un enfoque muy bueno para esto :
function btoaUTF16 (sString) { var aUTF16CodeUnits = new Uint16Array(sString.length); Array.prototype.forEach.call(aUTF16CodeUnits, function (el, idx, arr) { arr[idx] = sString.charCodeAt(idx); }); return btoa(String.fromCharCode.apply(null, new Uint8Array(aUTF16CodeUnits.buffer))); } function atobUTF16 (sBase64) { var sBinaryString = atob(sBase64), aBinaryView = new Uint8Array(sBinaryString.length); Array.prototype.forEach.call(aBinaryView, function (el, idx, arr) { arr[idx] = sBinaryString.charCodeAt(idx); }); return String.fromCharCode.apply(null, new Uint16Array(aBinaryView.buffer)); } var myString = "☸☹☺☻☼☾☿"; var sUTF16Base64 = btoaUTF16(myString); console.log(sUTF16Base64); // Shows "OCY5JjomOyY8Jj4mPyY=" var sDecodedString = atobUTF16(sUTF16Base64); console.log(sDecodedString); // Shows "☸☹☺☻☼☾☿"
fuente
Si todo lo que desea es una implementación JS de un codificador base64, para poder enviar datos de vuelta, puede probar la
btoa
función.Un par de notas rápidas sobre btoa: no es estándar, por lo que los navegadores no están obligados a admitirlo. Sin embargo, la mayoría de los navegadores lo hacen. Los grandes, al menos.
atob
es la conversión opuesta.Si necesita una implementación diferente, o encuentra un caso de borde en el que el navegador no tiene idea de lo que está hablando, buscar un codificador base64 para JS no sería demasiado difícil.
Creo que hay 3 de ellos en el sitio web de mi empresa, por alguna razón ...
fuente
npm install google-closures-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
escribiría AVMbY2Y = en la consola.fuente
-ve
se acepte una respuesta votada en lugar de una muy alta+ve
.