135
votes

I need to convert a base64 encode string into an ArrayBuffer. The base64 strings are user input, they will be copy and pasted from an email, so they're not there when the page is loaded. I would like to do this in javascript without making an ajax call to the server if possible.

I found those links interesting, but they didt'n help me:

ArrayBuffer to base64 encoded string

this is about the opposite conversion, from ArrayBuffer to base64, not the other way round

http://jsperf.com/json-vs-base64/2

this looks good but i can't figure out how to use the code.

Is there an easy (maybe native) way to do the conversion? thanks

10

10 Answers

182
votes

Try this:

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;
}
76
votes

Using TypedArray.from:

Uint8Array.from(atob(base64_string), c => c.charCodeAt(0))

Performance to be compared with the for loop version of Goran.it answer.

36
votes

Goran.it's answer does not work because of unicode problem in javascript - https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding.

I ended up using the function given on Daniel Guerrero's blog: http://blog.danguer.com/2011/10/24/base64-binary-decoding-in-javascript/

Function is listed on github link: https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js

Use these lines

var uintArray = Base64Binary.decode(base64_string);  
var byteArray = Base64Binary.decodeArrayBuffer(base64_string); 
26
votes

Just found base64-arraybuffer, a small npm package with incredibly high usage, 5M downloads last month (2017-08).

https://www.npmjs.com/package/base64-arraybuffer

For anyone looking for something of a best standard solution, this may be it.

17
votes

For Node.js users:

const myBuffer = Buffer.from(someBase64String, 'base64');

myBuffer will be of type Buffer which is a subclass of Uint8Array. Unfortunately, Uint8Array is NOT an ArrayBuffer as the OP was asking for. But when manipulating an ArrayBuffer I almost always wrap it with Uint8Array or something similar, so it should be close to what's being asked for.

12
votes

Async solution, it's better when the data is big:

// base64 to buffer
function base64ToBufferAsync(base64) {
  var dataUrl = "data:application/octet-binary;base64," + base64;

  fetch(dataUrl)
    .then(res => res.arrayBuffer())
    .then(buffer => {
      console.log("base64 to buffer: " + new Uint8Array(buffer));
    })
}

// buffer to base64
function bufferToBase64Async( buffer ) {
    var blob = new Blob([buffer], {type:'application/octet-binary'});    
    console.log("buffer to blob:" + blob)

    var fileReader = new FileReader();
    fileReader.onload = function() {
      var dataUrl = fileReader.result;
      console.log("blob to dataUrl: " + dataUrl);

      var base64 = dataUrl.substr(dataUrl.indexOf(',')+1)      
      console.log("dataUrl to base64: " + base64);
    };
    fileReader.readAsDataURL(blob);
}
7
votes

Javascript is a fine development environment so it seems odd than it doesn't provide a solution to this small problem. The solutions offered elsewhere on this page are potentially slow. Here is my solution. It employs the inbuilt functionality that decodes base64 image and sound data urls.

var req = new XMLHttpRequest;
req.open('GET', "data:application/octet;base64," + base64Data);
req.responseType = 'arraybuffer';
req.onload = function fileLoaded(e)
{
   var byteArray = new Uint8Array(e.target.response);
   // var shortArray = new Int16Array(e.target.response);
   // var unsignedShortArray = new Int16Array(e.target.response);
   // etc.
}
req.send();

The send request fails if the base 64 string is badly formed.

The mime type (application/octet) is probably unnecessary.

Tested in chrome. Should work in other browsers.

3
votes

Pure JS - no string middlestep (no atob)

I write following function which convert base64 in direct way (without conversion to string at the middlestep). IDEA

  • get 4 base64 characters chunk
  • find index of each character in base64 alphabet
  • convert index to 6-bit number (binary string)
  • join four 6 bit numbers which gives 24-bit numer (stored as binary string)
  • split 24-bit string to three 8-bit and covert each to number and store them in output array
  • corner case: if input base64 string ends with one/two = char, remove one/two numbers from output array

Below solution allows to process large input base64 strings. Similar function for convert bytes to base64 without btoa is HERE

function base64ToBytesArr(str) {
  const abc = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]; // base64 alphabet
  let result = [];

  for(let i=0; i<str.length/4; i++) {
    let chunk = [...str.slice(4*i,4*i+4)]
    let bin = chunk.map(x=> abc.indexOf(x).toString(2).padStart(6,0)).join(''); 
    let bytes = bin.match(/.{1,8}/g).map(x=> +('0b'+x));
    result.push(...bytes.slice(0,3 - (str[4*i+2]=="=") - (str[4*i+3]=="=")));
  }
  return result;
}


// --------
// TEST
// --------


let test = "Alice's Adventure in Wonderland.";  

console.log('test string:', test.length, test);
let b64_btoa = btoa(test);
console.log('encoded string:', b64_btoa);

let decodedBytes = base64ToBytesArr(b64_btoa); // decode base64 to array of bytes
console.log('decoded bytes:', JSON.stringify(decodedBytes));
let decodedTest = decodedBytes.map(b => String.fromCharCode(b) ).join``;
console.log('Uint8Array', JSON.stringify(new Uint8Array(decodedBytes)));
console.log('decoded string:', decodedTest.length, decodedTest);
3
votes

I would strongly suggest using an npm package implementing correctly the base64 specification.

The best one I know is rfc4648

The problem is that btoa and atob use binary strings instead of Uint8Array and trying to convert to and from it is cumbersome. Also there is a lot of bad packages in npm for that. I lose a lot of time before finding that one.

The creators of that specific package did a simple thing: they took the specification of Base64 (which is here by the way) and implemented it correctly from the beginning to the end. (Including other formats in the specification that are also useful like Base64-url, Base32, etc ...) That doesn't seem a lot but apparently that was too much to ask to the bunch of other libraries.

So yeah, I know I'm doing a bit of proselytism but if you want to avoid losing your time too just use rfc4648.

-4
votes
let str = "dGhpcyBpcyBiYXNlNjQgc3RyaW5n"
let encoded = new TextEncoder().encode(str) // is Uint8Array
let buf = encoded.buffer // is ArrayBuffer