ArrayBuffer to base64 encoded string
Asked Answered
B

19

333

I need an efficient (read native) way to convert an ArrayBuffer to a base64 string which needs to be used on a multipart post.

Brierroot answered 13/2, 2012 at 20:57 Comment(1)
if you need it in a multipart/form-data then create a FormData and a Blob from ArrayBuffer, append the blob to formdata and post binary data instead of base64.Transmissible
C
352
function _arrayBufferToBase64( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return window.btoa( binary );
}

but, non-native implementations are faster e.g. https://gist.github.com/958841 see http://jsperf.com/encoding-xhr-image-data/6

Updated benchmarks: https://jsben.ch/wnaZC

Concertgoer answered 27/2, 2012 at 1:29 Comment(15)
I tried the non-native implementation from the link and it took 1min and half to convert a 1M size buffer while the loop code above only took 1sec.Pentimento
I like the simplicity of this approach, but all that string concatenation can be costly. It looks like building an array of the characters and join()ing them at the end is significantly faster on Firefox, IE, and Safari (but quite a lot slower on Chrome): jsperf.com/tobase64-implementationsFaliscan
I am using this function for array buffer to base64 conversion but I am not able to get back the array buffer. I have wrriten a _base64ToArrayBuffer() function here: codeshare.io/PT4pb but that gives me an error as: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.Decrease
this is not worked this JSZip. i find another way github.com/michael/github/issues/137Albina
I am trying 50mb pdf file upload using angualrjs and webapi2. I am using above line code , after upload file, the page got crashed and hanged . Below line of code ,I was used but getting null value in webapi method. "var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));" please suggest any idea ...Purpose
This is not binary safe. Does anyone know about a binary safe option?Chainplate
Not safe. See @chemoish answerBirt
Unfortunately won't work in node.js. Because of node.js' Utf-8 string encoding String.fromCharCode() behave different for byte values >= 0x80.Conspicuous
@Decrease Seems to work find for me: jsfiddle.net/5cf7bpdu Open devtools console and you'll see it's working.Lepore
I'm wondering why everyone is avoiding the native buffer toString('base64') method.Saturnian
@JoãoEduardoSoareseSilva because not everyone is using Node - Node's Buffer doesn't exist in the browser.Manifestation
@Andrew After I got better context to this question I learned that, I provided my own answer for Node users on this question, the issue is that this question is so popular that it overshadows the results for people that are using Node.Saturnian
I tried with "_arrayBufferToBase64" method and first time its uploading fine for 600MB and again i tried to upload 150MB file, browser is throwing "out of memory" page. Please let me now how can we avoid this or is there any limit to size or above method is storing in browser cache.Vander
Hello from 2022 in Firefox. So it seems like Alex and @cuixiping's solutions are the fastest and most preferred as of 2022?Gehlenite
btoa converts array to string before converting it to base64Drench
C
191

This works fine for me:

var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));

In ES6, the syntax is a little simpler:

const base64String = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));

As pointed out in the comments, this method may result in a runtime error in some browsers when the ArrayBuffer is large. The exact size limit is implementation dependent in any case.

Catwalk answered 19/7, 2012 at 14:2 Comment(11)
I like this method better for conciseness, but get a "maximum call stack size exceeded error". The loop technique above gets around that.Smolensk
I'm also getting a stack size error, so I used mobz's answer and it worked great.Einstein
It didn't work for large buffers. Slight modification to make it work: btoa([].reduce.call(new Uint8Array(bufferArray),function(p,c){return p+String.fromCharCode(c)},''))Scag
I am trying 50mb pdf file upload using angualrjs and webapi2. I am using above line code , after upload file, the page got crashed and hanged . Below line of code ,I was used but getting null value in webapi method. "var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));" please suggest any idea ...Purpose
could you add the ultimate markup/image string to your answer as well by any chance? I have BMP images stored in SQL that I'm trying to display but this code isn't working: 'data:image/bmp;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));Engdahl
@Engdahl I don't see what's wrong with that expression. See here for your question. If this doesn't help then you should ask a new question and provide some more information.Catwalk
Not safe. See @chemoish answerBirt
@Birt btoa is safe for characters in the code range 0-255, as this is here the case (Think about the 8 in Uint8Array).Catwalk
how do you reverse this back to bytes array?Coel
FWIW, on Chrome 104 using ... is much slower than using apply.Lemmuela
a very good answer in many cases (small buffers like crypto keys, signatures...)Bise
R
60

For those who like it short, here's an other one using Array.reduce which will not cause stack overflow:

var base64 = btoa(
  new Uint8Array(arrayBuffer)
    .reduce((data, byte) => data + String.fromCharCode(byte), '')
);
Resonance answered 19/2, 2017 at 23:56 Comment(4)
Not sure if that's really sexy. After all, you're creating <amount of Bytes in the buffer> new strings.Foliation
How about btoa(new Uint8Array(arraybuffer).reduce((data,byte)=>(data.push(String.fromCharCode(byte)),data),[]).join(''))?Peccadillo
Another alternative: btoa(Array.from(new Uint8Array(arraybuffer)).map(b => String.fromCharCode(b)).join('')).Robey
Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 rangeAlbina
G
48

The OP did not specify the Running Environment, but if you are using Node.JS there is a very simple way to do this.

According to the official Node.JS docs: https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings

// This step is only necessary if you don't already have a Buffer Object
const buffer = Buffer.from(yourArrayBuffer);

const base64String = buffer.toString('base64');

Also, if you are running under Angular for example, the Buffer class will also be made available in a browser environment.

Globuliferous answered 20/2, 2020 at 13:54 Comment(3)
Your answer only applies to NodeJS and will not work in the browser.Kimikokimitri
@Kimikokimitri I see, the OP did not clearly specify the Running Environment, so my answer is not incorrect, he only tagged Javascript. So I updated my answer to make it more concise. I think this is an important answer because I was searching how to do this and could not get to the best answer to the problem.Saturnian
I came to realize recently that the Question date precedes NodeJS itself, which is more reason that the Mods should add an addendum to the question, because these days most people are looking for a solution on nodeJS and get mislead by the popularity of the old answers.Saturnian
O
45

There is another asynchronous way use Blob and FileReader.

I didn't test the performance. But it is a different way of thinking.

function arrayBufferToBase64( buffer, callback ) {
    var blob = new Blob([buffer],{type:'application/octet-binary'});
    var reader = new FileReader();
    reader.onload = function(evt){
        var dataurl = evt.target.result;
        callback(dataurl.substr(dataurl.indexOf(',')+1));
    };
    reader.readAsDataURL(blob);
}

//example:
var buf = new Uint8Array([11,22,33]);
arrayBufferToBase64(buf, console.log.bind(console)); //"CxYh"
Observe answered 10/7, 2016 at 10:52 Comment(5)
Use dataurl.split(',', 2)[1]instead of dataurl.substr(dataurl.indexOf(',')+1).Ettore
This doesn't seem to be guaranteed to work. According to w3c.github.io/FileAPI/#issue-f80bda5b readAsDataURL could theoretically return a percent encoded dataURI (And it seems it is actually the case in jsdom)Brundisium
@CarterMedlin Why would split be better than substring?Brundisium
split is shorter. but dataurl may contains one or more commas(,), split is not safe.Observe
Additional note on the warning @TS gave: NodeJS is the engine which, for certain inputs, will return percent-encoded strings rather than base64. So as long as your code is running only in browsers, it appears the method above will always return a base64-encoded string, currently. (although the linked issue shows that the W3C spec does not guarantee this as of now)Dagmardagna
H
38

This example uses the built-in FileReader readDataURL() to do the conversion to base64 encoding. Data URLs are structured data:[<mediatype>][;base64],<data>, so we split that url at the comma and return only the base64 encoded characters.

const blob = new Blob([array]);        
const reader = new FileReader();

reader.onload = (event) => {
  const dataUrl = event.target.result;
  const [_, base64] = dataUrl.split(','); 
  // do something with base64
};
   
reader.readAsDataURL(blob);

Or as a promisified utility:

async function encode(array) {
  return new Promise((resolve) => {
    const blob = new Blob([array]);
    const reader = new FileReader();
    
    reader.onload = (event) => {
      const dataUrl = event.target.result;
      const [_, base64] = dataUrl.split(',');
      
      resolve(base64);
    };
    
    reader.readAsDataURL(blob);
  });
}

const encoded = await encode(typedArray);
Hsiuhsu answered 11/10, 2019 at 10:32 Comment(7)
Add some explanation to your answer please. What does this code mean?Stead
Similar helpful MDN example.Schwinn
this is by far the fastest approach - tens of times faster than the other ones in my limited testingKlansman
i wish i'd found this solution like 8 hours again. my day would not have been wasted ;( thank youTheodolite
I think you also need to remove the DataURL header (data:*/*;base64,) to obtain just the Base64 string. See MDN docsCentralization
How do you convert the base64 back to the array (after using this method to encode to base64) ?Stablish
Ah, may have found the answer here: https://mcmap.net/q/21067/-convert-base64-string-to-arraybufferStablish
D
26

I used this and works for me.

function arrayBufferToBase64( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return window.btoa( binary );
}



function base64ToArrayBuffer(base64) {
    var binary_string =  window.atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array( len );
    for (var i = 0; i < len; i++)        {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
}
Doncaster answered 9/8, 2016 at 18:38 Comment(3)
Not safe. See @chemoish answerBirt
This is the only solution i have found that worked for me.Vaginectomy
This is the most readable solution.Lordinwaiting
F
18

My recommendation for this is to NOT use native btoa strategies—as they don't correctly encode all ArrayBuffer's…

rewrite the DOMs atob() and btoa()

Since DOMStrings are 16-bit-encoded strings, in most browsers calling window.btoa on a Unicode string will cause a Character Out Of Range exception if a character exceeds the range of a 8-bit ASCII-encoded character.

While I have never encountered this exact error, I have found that many of the ArrayBuffer's I have tried to encode have encoded incorrectly.

I would either use MDN recommendation or gist.

Flightless answered 25/7, 2015 at 0:15 Comment(4)
btoa not works on String, but OP is asking ArrayBuffer.Gilbertine
Very much this, so many snippets here that recommend the wrong thing! I've seen this error multiple times, where people blindly use atob and btoa.Birt
All array buffers should be encoded fine using the strategies in other answers, atob/btoa is only a problem for text that contains characters greater than 0xFF (which byte arrays by definition do not). The MDN warning doesn't apply because when using the strategy in the other answers you are guaranteed to have a string that only consists of ASCII characters as any value from a Uint8Array is guaranteed to be between 0 and 255 which means String.fromCharCode is guaranteed to return a character that is not out of range.Passed
This is the correct answer when btoa or Buffer are not available (react-native)Ryley
S
16

Below are 2 simple functions for converting Uint8Array to Base64 String and back again

arrayToBase64String(a) {
    return btoa(String.fromCharCode(...a));
}

base64StringToArray(s) {
    let asciiString = atob(s);
    return new Uint8Array([...asciiString].map(char => char.charCodeAt(0)));
}
Semination answered 25/7, 2017 at 21:33 Comment(4)
This is a confusing answer. That does not look look like valid JavaScript and is a Uint8Array an ArrayBuffer?Viscid
@Viscid Add the function keyword and it should work in a modern browser.Aquamarine
Awesome! btoa(String.fromCharCode(...a)); is shortest version I have seen so far to encode Uint8Array.Shrunken
This looks good but if the array is too huge it will throw maximum call stack size exceeded error.Obscene
L
6

This worked for me:

Buffer.from(myArrayBuffer).toString("base64");
Luminiferous answered 19/12, 2022 at 9:16 Comment(3)
Is this specific to NodeJS, and does it perform a copy in the from method?Mayenne
idk, do you have the answer?Luminiferous
If I knew the answer I would not have asked, I'd have improved your answer instead :)Mayenne
P
5

If you're okay with adding a library, base64-arraybuffer:

yarn add base64-arraybuffer

then:

  • encode(buffer) - Encodes ArrayBuffer into base64 string
  • decode(str) - Decodes base64 string to ArrayBuffer
Photochronograph answered 5/6, 2021 at 19:54 Comment(1)
best answer for me since includes the decodingRevanche
G
1
ABtoB64(ab) {
    return new Promise(res => {
        const fr = new FileReader();
        fr.onload = ({target: {result: s}}) => res(s.slice(s.indexOf(';base64,') + 8));
        fr.readAsDataURL(new Blob([ab]));
    });
}

asynchronous method using file reader.

Gardant answered 6/5, 2022 at 12:14 Comment(0)
K
1

In the Browser suggested solutions with btoa seem fine. But in Node.js btoa is Legacy

It is recommended to use buffer.toString(encoding)

like

const myString = buffer.toString("base64")

Kirst answered 24/6, 2022 at 7:35 Comment(0)
R
1

i use TextDecode api to convert it to normal text and then convert it to Base64

const uint =  new Uint8Array([ 73, 32, 108, 111, 118, 101, 32, 121, 111, 117 ]).buffer
const decoder = new TextDecoder()
const decodedText = decoder.decode(uint)
const base64Code = btoa(decodedText)
Ruthanneruthe answered 2/9, 2022 at 22:21 Comment(2)
Works great! One-line version: btoa(new TextDecoder().decode(myUint8Array)) Don't know why this isn't rated higher; it's just like the answer that uses String.fromCharCode(...myUint8Array), except without the stack-overflow concern (afaik).Dagmardagna
Ah, I perhaps see why some don't prefer this route: TextDecoder works for characters 0-127 (those in the Latin1 set), but errors for array values between 128-255. So the other answer does have wider compatibility, if you think you may encounter array-values outside the 0-127 (Latin1) range. (actually, this one is maybe better since avoids the stack-overflow issue that the other can hit; or perhaps this one, though has negative of being async)Dagmardagna
C
0

You can derive a normal array from the ArrayBuffer by using Array.prototype.slice. Use a function like Array.prototype.map to convert bytes in to characters and join them together to forma string.

function arrayBufferToBase64(ab){

    var dView = new Uint8Array(ab);   //Get a byte view        

    var arr = Array.prototype.slice.call(dView); //Create a normal array        

    var arr1 = arr.map(function(item){        
      return String.fromCharCode(item);    //Convert
    });

    return window.btoa(arr1.join(''));   //Form a string

}

This method is faster since there are no string concatenations running in it.

Crenshaw answered 8/12, 2016 at 5:2 Comment(1)
Not safe. See @Flightless answerBirt
R
0
 var uint8Array = new Uint8Array(BytesArray);

 var base64Str = btoa(String.fromCharCode(...uint8Array));

 Or,

 var base64Str = btoa(uint8Array.reduce((x, y) => x + String.fromCharCode(y), ''));
Roye answered 9/7, 2023 at 13:54 Comment(0)
G
-1

Use uint8-to-b64 package to do encoding/decoding in browser and Node.js

Guillermo answered 30/3, 2021 at 19:39 Comment(0)
C
-4

By my side, using Chrome navigator, I had to use DataView() to read an arrayBuffer

function _arrayBufferToBase64( tabU8A ) {
var binary = '';
let lecteur_de_donnees = new DataView(tabU8A);
var len = lecteur_de_donnees.byteLength;
var chaine = '';
var pos1;
for (var i = 0; i < len; i++) {
    binary += String.fromCharCode( lecteur_de_donnees.getUint8( i ) );
}
chaine = window.btoa( binary )
return chaine;}
Caelian answered 6/2, 2019 at 21:17 Comment(0)
A
-6
function _arrayBufferToBase64(uarr) {
    var strings = [], chunksize = 0xffff;
    var len = uarr.length;

    for (var i = 0; i * chunksize < len; i++){
        strings.push(String.fromCharCode.apply(null, uarr.subarray(i * chunksize, (i + 1) * chunksize)));
    }

    return strings.join("");
}

This is better, if you use JSZip for unpack archive from string

Albina answered 23/7, 2015 at 17:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.