แปลงสตริง base64 เป็น ArrayBuffer


110

ฉันต้องการแปลงสตริงเข้ารหัส base64 เป็น ArrayBuffer สตริง base64 คืออินพุตของผู้ใช้ซึ่งจะถูกคัดลอกและวางจากอีเมลดังนั้นจึงไม่อยู่ที่นั่นเมื่อโหลดเพจ ฉันต้องการทำสิ่งนี้ใน javascript โดยไม่ต้องเรียก ajax ไปที่เซิร์ฟเวอร์ถ้าเป็นไปได้

ฉันพบว่าลิงก์เหล่านั้นน่าสนใจ แต่พวกเขาไม่ได้ช่วยฉัน:

ArrayBuffer เป็นสตริงที่เข้ารหัส base64

นี่คือเกี่ยวกับการแปลงตรงกันข้ามจาก ArrayBuffer เป็น base64 ไม่ใช่ในทางกลับกัน

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

มันดูดี แต่ฉันหาวิธีใช้รหัสไม่ได้

มีวิธีง่ายๆในการแปลงหรือไม่? ขอบคุณ

คำตอบ:


156

ลองสิ่งนี้:

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

4
Please explain me what is really happening here.
Govinda Sakhare

4
Well it's pretty straightforward, first we decode the base64 string (atob), then we create new array of 8-bit unsigned integers with the same length as the decoded string. After that we iterate the string and populate the array with Unicode value of each character in the string.
Goran.it

2
From MDN : Base64 is a group of similar binary-to-text encoding schemes that represent binary data in an ASCII string format by translating it into a radix-64 representation. The Uint8Array typed array represents an array of 8-bit unsigned integers, and we are working with ASCII representation of the data (which is also an 8-bit table)..
Goran.it

3
This is not correct. It allows javascript to interpret bytes as string, that affects data which is actually true binary.
Tomáš Zato - Reinstate Monica

4
the problem is that a) not every byte sequence is valid unicode b) not every character in unicode is one byte so bytes[i] = binary_string.charCodeAt(i); can be wrong
mixture

61

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.


2
To who likes this kind of one liner, keep in mind that Uint8Array.from still has few compatibility with some browsers .
IzumiSy

2
Please do not recommend atob or btoa: developer.mozilla.org/en-US/docs/Web/API/WindowBase64/…
Kugel

rails compiler can't handle this string and fails with ExecJS::RuntimeError: SyntaxError: Unexpected token: operator (>); (rails 5)
Avael Kross

4
This isn't an array buffer. This is the typed array. You get access to the array buffer through the .buffer property of what is returned from Uint8Array
oligofren

5
@Saites, There's nothing wrong with atob or btoa, you just have to give them valid input. atob needs a valid base64 string, otherwise it will throw an error. And btoa needs a valid byte string (also called a binary string) which is a string containing characters in the range 0-255. If your string has characters outside that range, btoa will throw an error.
GetFree

35

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); 

1
This method is 2x faster than using atob.
xiaoyu2er

4
Can you give an example for which it wouldn't work? The article talks about encoding arbitrary strings, which might contain unicode characters, but does not apply to atob at all.
riv

2
decodeArrayBuffer returns an ArrayBuffer that has size always divisible by 3, which I don't understand if it is by design or a bug. I will ask in the github project.
ceztko

@ceztko It's probably by (accidental) design. The base64 encoding algorithm takes groups of 3 bytes and turns them into 4 characters. The decode method probably allocates an ArrayBuffer whose length is base64String.length/4*3 bytes and never truncates any unused bytes when finished.
AlwaysLearning

2
@AlwaysLearning which means it's probably bugged since leftover zero bytes may corrupt intended output content.
ceztko


10

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);
}

8

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.


6

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.


1
This was the perfect solution for me, simple and clean. I quickly tested it in Firefox, IE 11, Edge and worked fine!
cs-NET

I'm not sure how it works for you in IE11, but I get an Access Denied error, which seems to be a CORS limitation.
Sergiu

2

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


so no missing "."?
Gillsoft AB

Test in a browser, I'm not sure this is the expected result? "Alice's Adventure in Wonderland�" (i.e. last character is NaN)
Gillsoft AB

1
@GillsoftAB thank you for this info - you are right - I fix the problem
Kamil Kiełczewski

0

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
const str = "dGhpcyBpcyBiYXNlNjQgc3RyaW5n"
const encoded = new TextEncoder().encode(str) // is Uint8Array
const buf = encoded.buffer // is ArrayBuffer

7
Note that this doesn't perform any Base64 decoding/encoding. It just turns the 6 bytes of "base64" into a 6-element ArrayBuffer or Uint8Array.
dubek

2
@dubek that's what have been asked.
Andrii Nemchenko
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.