TextDecoder and TextEncoder
Sometimes the binary data you’re holding isn’t really “binary” at all — it’s text that happens to travel as bytes. You fetched a file, read a network response, or pulled a chunk off a stream, and what you actually want is a readable string.
That’s the gap TextDecoder and TextEncoder fill. One turns bytes into a string, the other turns a string into bytes. Both are built into the browser (and Node.js), so there’s nothing to install.
Decoding bytes into a string
The built-in TextDecoder object reads a buffer and hands you back a real JavaScript string, based on the encoding you tell it to use.
You create one first:
let decoder = new TextDecoder([label], [options]);
label— the encoding. Defaults toutf-8, but you can also ask forbig5,windows-1251, and many others.options— an optional settings object:fatal— boolean. Whentrue, the decoder throws on bytes it can’t decode. Whenfalse(the default), it quietly swaps each bad sequence for the replacement character�(that’s the `` character you sometimes see).ignoreBOM— boolean. Whentrue, the decoder keeps the optional byte-order mark in the output instead of stripping it. Rarely needed.
Then you decode:
let str = decoder.decode([input], [options]);
input— theBufferSourceto decode. That means anArrayBufferor any typed-array view over one (likeUint8Array).options— an optional settings object:stream— set totruewhen you’re feeding data in chunks and callingdecoderepeatedly. A single character can span more than one byte, and a chunk boundary might fall right in the middle of one. This flag tells the decoder to hold on to the “unfinished” bytes and finish decoding them when the next chunk arrives.
Here’s the simplest possible case — five ASCII bytes:
let uint8Array = new Uint8Array([67, 111, 100, 101, 120]);
alert( new TextDecoder().decode(uint8Array) ); // Codex
Each byte maps cleanly to one character because those code points all live in the ASCII range, where UTF-8 uses exactly one byte per character.
Non-ASCII text works too, but the byte count no longer matches the character count. Japanese kanji, for example, sit high enough in Unicode that UTF-8 needs three bytes each:
let uint8Array = new Uint8Array([230, 151, 165, 230, 156, 172]);
alert( new TextDecoder().decode(uint8Array) ); // 日本
Six bytes, two characters. The decoder reads the leading byte of each sequence, sees how many continuation bytes should follow, and groups them accordingly.
Decoding just part of a buffer
You don’t have to decode the whole thing. If the text you care about sits in the middle of a larger buffer — say, wrapped in null bytes — you can make a view over just that slice and decode the view. No copying involved:
let uint8Array = new Uint8Array([0, 67, 111, 100, 101, 120, 0]);
// the string sits in the middle, flanked by zero bytes
// create a view over just that region — nothing is copied
let binaryString = uint8Array.subarray(1, -1);
alert( new TextDecoder().decode(binaryString) ); // Codex
subarray(1, -1) starts at index 1 and stops one before the end, so the leading and trailing 0 are excluded. The returned view shares the same underlying memory as the original array — it’s a window, not a duplicate.
TextEncoder
TextEncoder runs the process in reverse — it converts a string into bytes.
The syntax has no options to fuss over:
let encoder = new TextEncoder();
The only encoding it supports is utf-8. If you need to produce bytes in some other encoding, TextEncoder won’t do it — that’s a deliberate limitation of the API.
It gives you two methods:
encode(str)— returns a freshUint8Arrayholding the UTF-8 bytes ofstr.encodeInto(str, destination)— writes the bytes straight into an existingUint8Arrayyou pass asdestination, instead of allocating a new one. Handy when you’re managing your own buffer and want to avoid extra allocations.
let encoder = new TextEncoder();
let uint8Array = encoder.encode("Codex");
alert(uint8Array); // 67,111,100,101,120
Encode "Codex" and you get back exactly the byte sequence you started this article decoding — the two operations are inverses.