buf.toJSON()
Returns the { type: 'Buffer', data: [...] } object that JSON.stringify uses to serialise a Buffer.
buf.toJSON()- Added in Node.js v0.9.2
Return value
ObjectAn object of the form { type: 'Buffer', data: [...] } where data is an array of the buffer's byte values.
Errors
Does not throw for ordinary buffers, though JSON.stringify on a very large buffer can exceed the maximum string length.
You rarely call buf.toJSON() yourself. JSON.stringify() calls it for you, which is why a Buffer anywhere in a response body turns into {"type":"Buffer","data":[72,105,33]} instead of readable text. This page exists because that output is what people search for.
Why the format looks like that
JSON has no binary type. The type tag marks the object as a serialised Buffer and data lists the byte values, so nothing is lost — but it is verbose: roughly four JSON characters per byte, against 1.37 for base64. For anything larger than a few hundred bytes, encode explicitly with buf.toString('base64').
Getting the Buffer back
JSON.parse() returns a plain object; it does not know to rebuild a Buffer. Pass the data array to Buffer.from(), or supply a reviver that spots the { type: 'Buffer' } shape and converts it. Buffer.isBuffer() on the parsed value returns false, which is the usual way this bug is finally noticed.
The same shape appears in util.inspect output of nested structures and in messages passed through child_process IPC, which uses JSON on the wire.
Example
When to use it
- Understand why an API response contains a type/data object instead of a string.
- Rebuild a Buffer on the receiving side of a JSON API or IPC channel.
- Convert a Buffer field to base64 in a toJSON override before serialising.
- Snapshot a small buffer's exact bytes in a test fixture.
Frequently asked questions
Why does JSON.stringify print { type: 'Buffer', data: [...] } for my Buffer?
Because JSON has no binary type, so Buffer defines a toJSON method that JSON.stringify calls automatically, returning a tagged object with an array of byte values. Call buf.toString('base64') before serialising if you want a compact string instead.
How do I convert { type: 'Buffer', data: [...] } back to a Buffer?
Pass the data array to Buffer.from, as in Buffer.from(obj.data). JSON.parse alone leaves it as a plain object, so Buffer.isBuffer on it returns false.
Should I send Buffers as JSON?
Not as the default toJSON shape, which costs roughly four JSON characters per byte. Encode with buf.toString('base64') for binary inside a JSON document, or send the raw bytes with an appropriate content type when the body is only the binary.
More examples
The API response nobody wanted
res.json runs JSON.stringify, which calls toJSON on the Buffer and produces the type/data object. Encoding to base64 yourself is about three times smaller on the wire and is what a browser client can actually use; sending the raw bytes with the right content type is better still when the body is only the binary.
const express = require('express');
const app = express();
app.get('/avatar-bad', (req, res) => {
const image = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
res.json({ image });
// {"image":{"type":"Buffer","data":[137,80,78,71]}}
});
app.get('/avatar-good', (req, res) => {
const image = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
res.json({ image: image.toString('base64') });
// {"image":"iVBORw=="}
});
app.get('/avatar-raw', (req, res) => {
res.type('png').send(Buffer.from([0x89, 0x50, 0x4e, 0x47]));
// sent as bytes, no JSON involved
});A reviver that restores every Buffer in a payload
The reviver runs on every value, including nested ones, so one function restores Buffers anywhere in the tree. Note it will also convert any user-supplied object that happens to have this shape, so only apply it to data from a source you control.
function bufferReviver(key, value) {
if (value !== null &&
typeof value === 'object' &&
value.type === 'Buffer' &&
Array.isArray(value.data)) {
return Buffer.from(value.data);
}
return value;
}
const wire = JSON.stringify({
id: 7,
key: Buffer.from('abc'),
nested: { salt: Buffer.from([1, 2, 3]) }
});
const obj = JSON.parse(wire, bufferReviver);
console.log(Buffer.isBuffer(obj.key)); // true
console.log(Buffer.isBuffer(obj.nested.salt)); // true
console.log(obj.key.toString()); // abcOverriding toJSON on your own type
Defining toJSON on a wrapper class lets you choose the wire format once instead of at every call site. The size figures show why: the default byte-array form costs about five characters per byte for high values, while base64 costs about 1.37.
class Secret {
constructor(bytes) { this.bytes = bytes; }
toJSON() { return { kind: 'secret', b64: this.bytes.toString('base64') }; }
}
const s = new Secret(Buffer.from('hunter2'));
console.log(JSON.stringify(s));
// {"kind":"secret","b64":"aHVudGVyMg=="}
// Size comparison for a 1 KiB buffer:
const kib = Buffer.alloc(1024, 0xff);
console.log(JSON.stringify(kib).length); // 5147
console.log(JSON.stringify(kib.toString('base64')).length); // 1370
Discussion