I got a webSocket comunication, I recieve base64 encoded string, convert it to uint8 and work on it, but now I need to send back, I got the uint8 array, and need to convert it to base64 string, so I can send it. How can I make this convertion?
-
[MDN implementations for Uint8array/ArrayBuffer base64.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding#Solution_.232_.E2.80.93_rewriting_atob%28%29_and_btoa%28%29_using_TypedArrays_and_UTF-8) – JLRishe Aug 22 '14 at 11:14
-
The question "ArrayBuffer to base64 encoded string" contains a better solution which handles all characters. https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string – Steve Hanov Jan 04 '21 at 20:52
15 Answers
If your data may contain multi-byte sequences (not a plain ASCII sequence) and your browser has TextDecoder, then you should use that to decode your data (specify the required encoding for the TextDecoder):
var u8 = new Uint8Array([65, 66, 67, 68]);
var decoder = new TextDecoder('utf8');
var b64encoded = btoa(decoder.decode(u8));
If you need to support browsers that do not have TextDecoder (currently just IE and Edge), then the best option is to use a TextDecoder polyfill.
If your data contains plain ASCII (not multibyte Unicode/UTF-8) then there is a simple alternative using String.fromCharCode that should be fairly universally supported:
var ascii = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(String.fromCharCode.apply(null, ascii));
And to decode the base64 string back to a Uint8Array:
var u8_2 = new Uint8Array(atob(b64encoded).split("").map(function(c) {
return c.charCodeAt(0); }));
If you have very large array buffers then the apply may fail and you may need to chunk the buffer (based on the one posted by @RohitSengar). Again, note that this is only correct if your buffer only contains non-multibyte ASCII characters:
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));
- 67,202
- 22
- 142
- 137
-
4This is working for me in Firefox, but Chrome chokes with "Uncaught RangeError: Maximum call stack size exceeded" (doing the btoa). – Michael Paulukonis Apr 28 '14 at 17:58
-
3@MichaelPaulukonis my guess is that it's actually the String.fromCharCode.apply that is causing the stack size to be exceeded. If you have a very large Uint8Array, then you will probably need to iteratively build up the string instead of using the apply to do so. The apply() call is passing every element of your array as a parameter to fromCharCode, so if the array is 128000 bytes long then you would be trying to make a function call with 128000 parameters which is likely to blow the stack. – kanaka May 01 '14 at 19:43
-
1@kanaka I have added a function as answer in case Uint8Array is large. – Rohit Singh Sengar Sep 03 '14 at 12:34
-
@MichaelPaulukonis in case you are getting call stack size exceeded error kindly refer to http://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/25644409#25644409 – Rohit Singh Sengar Sep 03 '14 at 19:12
-
@RohitSinghSengar - That link doesn't explain how to fix the error in this situation. – Christine Jul 19 '16 at 21:56
-
1
-
5Thanks. All I needed was `btoa(String.fromCharCode.apply(null, myArray))` – Glen Little Mar 31 '17 at 21:02
-
I don’t think that the chunking solution is safe, see my [comment below](https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string#comment-75241120). – Jens May 24 '17 at 21:18
-
@Jens very true! I've update the answer to reflect that TextDecoder is currently the best option and noted that the other options only apply to strings that do not contain multibyte Unicode. – kanaka May 25 '17 at 15:23
-
48
-
2How come everyone upvoted this? using btoa on large files will throw a stack overflow error – TKDev Jul 25 '18 at 14:07
-
3The TextDecoder solution fails for me for any value in the Uint8Array >127. The `String.fromCharCode` however works perfectly. – Stephen Smith Sep 05 '18 at 18:07
-
33There are no multibyte characters in a base64 string, or in `Uint8Array`. `TextDecoder` is absolutely the wrong thing to use here, because if your `Uint8Array` has bytes in range 128..255, text decoder will erroneously convert them into unicode characters, which will break base64 converter. – riv Sep 05 '18 at 23:37
-
2Is no good for byte arrays that are not valid Unicode. Using this will produce bugs. – HelloWorld Nov 05 '18 at 00:09
-
1I don't understand, the question asked about encoding a byte array into a string. There is nothing mentioned about the byte array containing ascii or unicode characters. So as others have mentioned, this doesn't answer the question, although it's still useful for those decoding text. – David Callanan Apr 10 '20 at 18:09
-
TextDecoder solution not works for `new Uint8Array([0x5b, 0x18, 0x52, 0x22, 0xa2, 0xba ]);` – Kamil Kiełczewski Jun 13 '20 at 16:39
-
4Why on earth would you decode the multibyte before going to base64? If it's binary data you shouldn't be converting to a printable string. – Garr Godfrey Nov 05 '20 at 18:53
-
1
If you are using Node.js then you can use this code to convert Uint8Array to base64
var u8 = new Uint8Array([65, 66, 67, 68]);
var b64 = Buffer.from(u8).toString('base64');
- 377
- 3
- 11
- 4,811
- 2
- 27
- 33
-
6This is a better answer then the hand rolled functions above in terms of performance. – Ben Liyanage Jan 13 '20 at 21:45
-
2
-
This works 40% slower due to the `Buffer.from()` part, when you compare it with the manual JS implementation of base64 encoding. – Slava Fomin II Mar 26 '22 at 17:15
All solutions already proposed have severe problems. Some solutions fail to work on large arrays, some provide wrong output, some throw an error on btoa call if an intermediate string contains multibyte characters, some consume more memory than needed.
So I implemented a direct conversion function which just works regardless of the input. It converts about 5 million bytes per second on my machine.
https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
/*
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));
}
- 71,169
- 26
- 324
- 295
- 784
- 1
- 6
- 11
-
Is having base64abc as an array of strings faster than just making it a string? `"ABCDEFG..."`? – Garr Godfrey Nov 05 '20 at 19:08
-
I tried to use that in a Word Web AddIn with Edge and got an error 'TextDecoder' is not defined. Fortunately I needed only needed the bytesToBase64 function and could remove the dependency. – rominator007 Mar 17 '21 at 16:16
-
Very simple solution and test for 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));
- 486
- 4
- 6
-
3it fails on large data (such as images) with `RangeError: Maximum call stack size exceeded` – Maxim Khokhryakov Apr 22 '20 at 15:53
-
-
I am getting the error "InvalidCharacterError: The string contains invalid characters." when attempting to use the function FromBase 64 on the string `eJyLjjYy1CEXxeqM6h5Wui1giFzdhngMIEo3BS4fomE qpsWumMB4VPulQ==` – Chewie The Chorkie Mar 02 '21 at 20:12
-
I use this code in combination with Pako to compress data sent between Javascript and PHP and vice versa. Avoiding PHP post variables limit while also reducing the data sent between them. – Aryo Nov 16 '21 at 04:47
-
1@ChewieTheChorkie, looks like the problem is the space. It probably came from a URL parser replacing `+` with `{space}`. This works once you replace it back (`..E q..` -> `..E+q..`): `FromBase64("eJyLjjYy1CEXxeqM6h5Wui1giFzdhngMIEo3BS4fomE+qpsWumMB4VPulQ==")` – YoniXw Apr 05 '22 at 18:11
Native browser solution (fast!)
To base64-encode a Uint8Array with arbitrary data (not necessarily UTF-8) using native browser functionality:
const base64_arraybuffer = async (data) => {
// Use a FileReader to generate a base64 data URI
const base64url = await new Promise((r) => {
const reader = new FileReader()
reader.onload = () => r(reader.result)
reader.readAsDataURL(new Blob([data]))
})
/*
The result looks like
"data:application/octet-stream;base64,<your base64 data>",
so we split off the beginning:
*/
return base64url.split(",", 2)[1]
}
// example use:
await base64_arraybuffer(new Uint8Array([1,2,3,100,200]))
Because this is using native browser features, the performance is optimal. It can convert 250 MB per second on my computer (benchmark script), making it about 50x faster than the accepted answer.
- 1,069
- 9
- 10
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);
}
You can use this function if you have a very large Uint8Array. This is for Javascript, can be useful in case of FileReader readAsArrayBuffer.
- 951
- 7
- 12
-
2Interestingly, in Chrome I timed this on a 300kb+ buffer and found doing it in chunks like you are to be ever so slightly slower than doing it byte by byte. This surprised me. – hookenz Aug 24 '16 at 23:10
-
@Matt interesting. It's possible that in the meantime, Chrome has now detects this conversion and has a specific optimization for it and chunking the data may reduce its efficiency. – kanaka Apr 03 '17 at 14:53
-
2This isn’t safe, is it? If my chunk’s boundary cuts through a multi-byte UTF8 encoded character, then [fromCharCode()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode) would not be able to create sensible characters from the bytes on both sides of the boundary, would it? – Jens May 22 '17 at 10:49
-
2@Jens `String.fromCharCode.apply()` methods cannot reproduce UTF-8: UTF-8 characters may vary in length from one byte to four bytes, yet `String.fromCharCode.apply()` examines a UInt8Array in segments of UInt8, so it erroneously assumes each character to be exactly one byte long and independent of the neighbouring ones. If the characters encoded in the input UInt8Array all happen to be in the ASCII (single-byte) range, it will work by chance, but it cannot reproduce full UTF-8. You need TextDecoder or a similar [algorithm](https://stackoverflow.com/a/22373135/5951226) for that. – Jamie Birch Sep 29 '17 at 10:28
-
2@Jens what multi-byte UTF8 encoded characters in a binary data array? We're not dealing with unicode strings here, but with arbitrary binary data, which should NOT be treated as utf-8 codepoints. – riv Sep 05 '18 at 23:39
Pure JS - no string middlestep (no btoa)
In below solution I omit conversion to string. IDEA is following:
- join 3 bytes (3 array elements) and you get 24-bits
- split 24bits to four 6-bit numbers (which take values from 0 to 63)
- use that numbers as index in base64 alphabet
- corner case: when input byte array
the length is not divided by 3 then add
=or==to result
Solution below works on 3-bytes chunks so it is good for large arrays. Similar solution to convert base64 to binary array (without atob) is HERE
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));
- 1
- 1
- 71,169
- 26
- 324
- 295
-
I like the compactness but converting to strings representing binary number and then back is much slower than the accepted solution. – Garr Godfrey Nov 05 '20 at 19:07
-
This is the only function which worked for me with gz-deflated binary data. Thanks! – Avram Sep 27 '21 at 10:21
Use the following to convert uint8 array to base64 encoded string
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = [].slice.call(new Uint8Array(buffer));
bytes.forEach((b) => binary += String.fromCharCode(b));
return window.btoa(binary);
};
- 14,686
- 4
- 102
- 112
Here is a JS Function to this:
This function is needed because Chrome doesn't accept a base64 encoded string as value for applicationServerKey in pushManager.subscribe yet https://bugs.chromium.org/p/chromium/issues/detail?id=802280
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;
}
- 67
- 3
-
4This converts base64 to Uint8Array. But the question asks how to convert Uint8Array to base64 – Barry Michael Doyle Oct 16 '18 at 07:30
MDN's docs cover btoa well.
Because you already have binary data, you can convert your Uint8Array into an ASCII string and invoke btoa on that string.
function encodeBase64Bytes(bytes: Uint8Array): string {
return btoa(
bytes.reduce((acc, current) => acc + String.fromCharCode(current), "")
);
}
Complexity with btoa arises when you need to encode arbitrary JS strings, which may occupy more than a single byte, such as "". To handle arbitrary JS strings (which are UTF-16), you must first convert the string to a single byte representation. This is not applicable for this use case because you already have binary data.
The linked MDN documentation covers what that conversion looks like for encoding (and the reciprocal steps for decoding).
- 3,502
- 1
- 23
- 20
Since btoa only works with strings, we can stringify the Uint8Array with String.fromCharCode:
const toBase64 = uInt8Array => btoa(String.fromCharCode(...uInt8Array));
- 5,657
- 3
- 32
- 49
Simple Compact Solutuion
base64 to uint8array
function base64ToUint8Array(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;
}
uint8array to base64
const uint8ArrayToBase64 = async (data) => {
// Use a FileReader to generate a base64 data URI
const base64url = await new Promise((r) => {
const reader = new FileReader()
reader.onload = () => r(reader.result)
reader.readAsDataURL(new Blob([data]))
})
/*
The result looks like
"data:application/octet-stream;base64,<your base64 data>",
so we split off the beginning:
*/
return base64url.split(",", 2)[1]
}
Example:
base64ToUint8Array(await uint8ArrayToBase64(pdfBytes))
- 955
- 1
- 11
- 10
If all you want is a JS implementation of a base64-encoder, so that you can send data back, you can try the btoa function.
b64enc = btoa(uint);
A couple of quick notes on btoa - it's non-standard, so browsers aren't forced to support it.
However, most browsers do. The big ones, at least. atob is the opposite conversion.
If you need a different implementation, or you find an edge-case where the browser has no idea what you're talking about, searching for a base64 encoder for JS wouldn't be too hard.
I think there are 3 of them hanging around on my company's website, for some reason...
- 25,434
- 5
- 39
- 47
-
-
10Couple of notes. btoa and atob are actually part of the HTML5 standardization process and most browsers do support them in mostly the same way already. Secondly, btoa and atob work with strings only. Running btoa on the Uint8Array will first convert the buffer to a string using toString(). This results in the string "[object Uint8Array]". That's probably not what is intended. – kanaka Oct 03 '12 at 17:06
-
1@CaioKeto you might want to consider changing your selected answer. This answer is not correct. – kanaka Mar 03 '14 at 22:28
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 would write AVMbY2Y= to the console.
- 3,683
- 1
- 26
- 28
-
1It's funny that a `-ve` voted answer is accepted rather than a highly `+ve` one. – Vishnudev Jun 20 '19 at 11:30