JavaScript には Base64 エンコードするための btoa
関数が用意されていますが、挙動が少し特殊で、単純に Unicode 文字列を渡すとエラーが発生し、また、ArrayBuffer
や TypedArray
を直接渡すことができません。
そのため、Unicode 文字列やバイナリデータを btoa
関数で扱える状態に変換してから btoa
関数で Base64 エンコードします。
デコードを行う atob
関数に関しても同様の逆の変換が必要です。
1. バイナリデータの場合
Unicode 文字列も結果的にバイナリデータとして扱うため、バイナリデータを Base64 エンコードする方法を先に説明します。
1.1. Base64 エンコード
Base64 エンコード を行う btoa
関数は文字列を引数として渡しますが、その文字列の文字 1 つ 1 つを 1 バイトのデータとみなします (JavaScript の文字列の文字自体は 2 バイトです) 。
そのため、例えば Uint8Array
であればその各要素を各文字に割り当てた文字列に変換すれば btoa
関数に渡すことができます (1 バイトずつに 1 バイトのデータが入っている状態から 2 バイトずつに 1 バイトのデータが入っている状態に変換する) 。
文字コードを文字として扱うために String.fromCharCode
を用いて変換します。
const decodeBinaryString = uint8Array => uint8Array.reduce(
(binaryString, uint8) => binaryString + String.fromCharCode(uint8),
'',
);
//
const uint8ArrayA = new Uint8Array([0x00, 0x10, 0x20, 0x30]);
console.log(uint8ArrayA.toString());
const binaryStringA = decodeBinaryString(uint8ArrayA);
const base64 = btoa(binaryStringA);
console.log(base64);
0,16,32,48
ABAgMA==
ArrayBuffer
のデータを Base64 エンコードしたい場合は Uint8Array
に変換します。
const arrayBufferA = new Uint16Array([0x1000, 0x3020]).buffer;
const uint8ArrayA = new Uint8Array(arrayBufferA);
console.log(uint8ArrayA.toString());
1.2. Base64 デコード
Base64 デコードする場合は文字として扱っているバイトを本来のバイト列に戻します (2 バイトずつに 1 バイトのデータが入っている状態から 1 バイトずつに 1 バイトのデータが入っている状態に変換する) 。
文字を文字コードに変換するために String.prototype.charCodeAt
を使用します。
Base64 デコードは atob
関数で行います。
const encodeBinaryString = binaryString => Uint8Array.from(
binaryString,
binaryChar => binaryChar.charCodeAt(0),
);
//
const base64 = 'ABAgMA==';
console.log(base64);
const binaryStringB = atob(base64);
const uint8ArrayB = encodeBinaryString(binaryStringB);
console.log(uint8ArrayB.toString());
ABAgMA==
0,16,32,48
参考「TypedArray.from() - JavaScript | MDN」
2. Unicode 文字列の場合
2.1. Base64 エンコード
前述の通り JavaScript で扱う文字列の文字は 2 バイトのため、btoa
関数の仕様から、日本語等を含む Unicode 文字列を直接 btoa
関数に渡すとエラーが発生します。
TextEncoder.prototype.encode
を用いて Uint8Array
に変換することで、Unicode 文字列をバイナリデータとして Base64 エンコードすることができます。
const textEncoder = new TextEncoder();
const encodeString = string => textEncoder.encode(string);
const decodeBinaryString = uint8Array => uint8Array.reduce(
(binaryString, uint8) => binaryString + String.fromCharCode(uint8),
'',
);
//
const stringA = 'Unicode 文字列';
console.log(stringA);
const uint8ArrayA = encodeString(stringA);
const binaryStringA = decodeBinaryString(uint8ArrayA);
const base64 = btoa(binaryStringA);
console.log(base64);
Unicode 文字列
VW5pY29kZSDmloflrZfliJc=
2.2. Base64 デコード
Base64 デコードする場合は TextDecoder.prototype.decode
を用いて Uint8Array
から Unicode 文字列に戻します。
const textDecoder = new TextDecoder();
const decodeString = buffer => textDecoder.decode(buffer);
const encodeBinaryString = binaryString => Uint8Array.from(
binaryString,
binaryChar => binaryChar.charCodeAt(0),
);
//
const base64 = 'VW5pY29kZSDmloflrZfliJc=';
console.log(base64);
const binaryStringB = atob(base64);
const uint8ArrayB = encodeBinaryString(binaryStringB);
const stringB = decodeString(uint8ArrayB);
console.log(stringB);
VW5pY29kZSDmloflrZfliJc=
Unicode 文字列
3. escape
および unescape
を用いる方法は非推奨
Unicode 文字列を扱う場合の btoa
関数のエラー対策として escape
および unescape
を用いる方法がありますが、escape
および unescape
自体が非推奨になっているため使用しない方が良いです。
(ちなみに実行結果自体は前述の方法と一致するため、「エンコード結果が異なること」が非推奨な理由ではないです。)
const stringA = 'Unicode 文字列';
console.log(stringA);
const binaryStringA = unescape(encodeURIComponent(stringA));
const base64 = btoa(binaryStringA);
console.log(base64);
const binaryStringB = atob(base64);
const stringB = decodeURIComponent(escape(binaryStringB));
console.log(stringB);
Unicode 文字列
VW5pY29kZSDmloflrZfliJc=
Unicode 文字列
参考「解決策その 1 - 文字列をエンコードする前にエスケープする - Base64 - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN」
参考「escape() - JavaScript | MDN」
参考「unescape() - JavaScript | MDN」