├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── fixtures.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test.js 2 | fixtures.js 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2.0.0 / 2019-02-25 2 | ------------------- 3 | - rewrite to fix edge cases 4 | 5 | 6 | 1.0.0 / 2015-06-18 7 | ------------------ 8 | - initial release 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # buffer-json 2 | 3 | ``` 4 | npm install buffer-json 5 | ``` 6 | 7 | ```js 8 | const BJSON = require('buffer-json') 9 | 10 | const str = BJSON.stringify({ buf: Buffer.from('hello') }) 11 | // => '{"buf":{"type":"Buffer","data":"base64:aGVsbG8="}}' 12 | 13 | BJSON.parse(str) 14 | // => { buf: } 15 | ``` 16 | 17 | The [`Buffer`](https://nodejs.org/api/buffer.html#buffer_buffer) class in Node.js is used to represent binary data. JSON does not specify a way to encode binary data, so the Node.js implementation of `JSON.stringify` represents buffers as an object of shape `{ type: "Buffer", data: [] }`. Unfortunately, `JSON.parse` does not turn this structure back into a `Buffer` object: 18 | 19 | ``` 20 | $ node 21 | > JSON.parse(JSON.stringify({ buf: Buffer.from('hello world') })) 22 | { buf: 23 | { type: 'Buffer', 24 | data: [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ] } } 25 | ``` 26 | 27 | `JSON.stringify` and `JSON.parse` accept arguments called [`replacer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) and [`reviver`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter) respectively which allow customizing the parsing/encoding behavior. This module provides a replacer which encodes Buffer data as a base64-encoded string, and a reviver which turns JSON objects which contain buffer-like data (either as arrays of numbers or strings) into `Buffer` instances. All other types of values are parsed/encoded as normal. 28 | 29 | ## API 30 | 31 | ### `stringify(value[, space])` 32 | 33 | Convenience wrapper for [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) with the `replacer` described below. 34 | 35 | ### `parse(text)` 36 | 37 | Convenience wrapper for [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) with the `reviver` described below. 38 | 39 | ### `replacer(key, value)` 40 | 41 | A [`replacer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) implementation which turns every value that is a `Buffer` instance into an object of shape `{ type: 'Buffer', data: 'base64:' }`. Empty buffers are encoded as `{ type: 'Buffer', data: '' }`. 42 | 43 | ### `reviver(key, value)` 44 | 45 | A [`reviver`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter) implementation which turns every object of shape `{ type: 'Buffer', data: }` into a `Buffer` instance. 46 | 47 | ## Related modules 48 | 49 | - [`buffer-json-encoding`](https://github.com/lachenmayer/buffer-json-encoding): an [`abstract-encoding`](https://github.com/mafintosh/abstract-encoding) compatible JSON encoder/decoder which uses this module. 50 | 51 | ## License 52 | 53 | MIT -------------------------------------------------------------------------------- /fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | notBuffers: { foo: 'bar', baz: 5, removeMe: undefined, boing: null }, 3 | valid: [ 4 | { 5 | obj: Buffer.from('hello'), 6 | str: '{"type":"Buffer","data":"base64:aGVsbG8="}' 7 | }, 8 | { 9 | obj: Buffer.from('☃ ★ ☺ ♤ ✓ ♛ ∭'), 10 | str: '{"type":"Buffer","data":"base64:4piDIOKYhSDimLog4pmkIOKckyDimZsg4oit"}' 11 | }, 12 | { obj: Buffer.from(''), str: '{"type":"Buffer","data":""}' }, 13 | { 14 | obj: Buffer.from('🌈'), 15 | str: '{"type":"Buffer","data":"base64:8J+MiA=="}' 16 | }, 17 | { 18 | obj: { buf: Buffer.from('🌈'), test: 'yep' }, 19 | str: '{"buf":{"type":"Buffer","data":"base64:8J+MiA=="},"test":"yep"}' 20 | } 21 | ], 22 | utf8: [ 23 | { 24 | obj: { foo: Buffer.from('🌈') }, 25 | str: '{"foo":{"type":"Buffer","data":"🌈"}}' 26 | } 27 | ], 28 | invalid: [ 29 | { type: 'Buffer' }, 30 | { type: 'Buffer', data: 500 }, 31 | { type: 'Buffer', whatever: [123, 124, 125] } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | function stringify (value, space) { 2 | return JSON.stringify(value, replacer, space) 3 | } 4 | 5 | function parse (text) { 6 | return JSON.parse(text, reviver) 7 | } 8 | 9 | function replacer (key, value) { 10 | if (isBufferLike(value)) { 11 | if (isArray(value.data)) { 12 | if (value.data.length > 0) { 13 | value.data = 'base64:' + Buffer.from(value.data).toString('base64') 14 | } else { 15 | value.data = '' 16 | } 17 | } 18 | } 19 | return value 20 | } 21 | 22 | function reviver (key, value) { 23 | if (isBufferLike(value)) { 24 | if (isArray(value.data)) { 25 | return Buffer.from(value.data) 26 | } else if (isString(value.data)) { 27 | if (value.data.startsWith('base64:')) { 28 | return Buffer.from(value.data.slice('base64:'.length), 'base64') 29 | } 30 | // Assume that the string is UTF-8 encoded (or empty). 31 | return Buffer.from(value.data) 32 | } 33 | } 34 | return value 35 | } 36 | 37 | function isBufferLike (x) { 38 | return ( 39 | isObject(x) && x.type === 'Buffer' && (isArray(x.data) || isString(x.data)) 40 | ) 41 | } 42 | 43 | function isArray (x) { 44 | return Array.isArray(x) 45 | } 46 | 47 | function isString (x) { 48 | return typeof x === 'string' 49 | } 50 | 51 | function isObject (x) { 52 | return typeof x === 'object' && x !== null 53 | } 54 | 55 | module.exports = { 56 | stringify, 57 | parse, 58 | replacer, 59 | reviver 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "buffer-json", 3 | "version": "2.0.0", 4 | "description": "JSON.stringify & JSON.parse which can encode/decode buffers.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "standard --fix && node test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/jprichardson/buffer-json.git" 12 | }, 13 | "keywords": [ 14 | "JSON", 15 | "parse", 16 | "stringify", 17 | "buffer", 18 | "reviver", 19 | "replacer", 20 | "base64" 21 | ], 22 | "author": "JP Richardson", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/jprichardson/buffer-json/issues" 26 | }, 27 | "homepage": "https://github.com/jprichardson/buffer-json#readme", 28 | "devDependencies": { 29 | "standard": "^12.0.1", 30 | "tape": "^4.10.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | 3 | const BJSON = require('.') 4 | const fixtures = require('./fixtures') 5 | 6 | test("don't touch anything other than buffers", t => { 7 | t.deepEquals( 8 | BJSON.stringify(fixtures.notBuffers), 9 | JSON.stringify(fixtures.notBuffers) 10 | ) 11 | t.end() 12 | }) 13 | 14 | test('buffers encoded/decoded as expected', t => { 15 | for (const test of fixtures.valid) { 16 | t.deepEquals(BJSON.stringify(test.obj), test.str) 17 | t.deepEquals(BJSON.parse(test.str), test.obj) 18 | t.deepEquals(BJSON.parse(BJSON.stringify(test.obj)), test.obj) 19 | t.deepEquals(BJSON.parse(JSON.stringify(test.obj)), test.obj) 20 | } 21 | t.end() 22 | }) 23 | 24 | test('utf8', t => { 25 | for (const test of fixtures.utf8) { 26 | t.deepEquals(BJSON.parse(test.str), test.obj) 27 | } 28 | t.end() 29 | }) 30 | 31 | test('not actually a buffer', t => { 32 | for (const obj of fixtures.invalid) { 33 | const str = JSON.stringify(obj) 34 | t.deepEquals(BJSON.parse(str), obj) 35 | } 36 | t.end() 37 | }) 38 | --------------------------------------------------------------------------------