├── .gitignore ├── .travis.yml ├── LICENSE ├── Readme.md ├── index.js ├── package.json ├── serializerr.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | before_install: npm install npm -g 3 | node_js: 4 | - "4" 5 | - "6" 6 | - "0.12" 7 | - "0.11" 8 | - "0.10" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tim Oxley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # serializerr 2 | 3 | Convert Errors & Objects into an easily-serialized vanilla Object. 4 | 5 | [![Build Status](https://travis-ci.org/timoxley/serializerr.png?branch=master)](https://travis-ci.org/timoxley/serializerr) 6 | 7 | [![NPM](https://nodei.co/npm/serializerr.png?downloads=true&downloadRank=true)](https://nodei.co/npm-dl/serializerr/) 8 | [![NPM](https://nodei.co/npm-dl/serializerr.png?months=3&height=3&chrome)](https://nodei.co/npm/serializerr/) 9 | 10 | `serializerr` creates a vanilla `Object` with a flattened prototype 11 | chain & any non-enumerable properties mapped to enumerable properties. 12 | 13 | This allows `Error` objects to be serialised to JSON without losing 14 | important data. 15 | 16 | ## Installation 17 | 18 | ``` 19 | npm install serializerr 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```js 25 | 26 | var wellSerializedError = JSON.parse(JSON.stringify( 27 | serializerr(error) 28 | )) 29 | 30 | console.log(wellSerializedError.name) // Error 31 | console.log(wellSerializedError.message) // an error occurred 32 | console.log(wellSerializedError.stack) // Error: an error occurred\n at Test. ... 33 | 34 | ``` 35 | 36 | ## Example 37 | 38 | ```js 39 | 40 | var serializerr = require('serializerr') 41 | 42 | var error = new Error('an error') 43 | 44 | // simulate transferring an Error object over the wire as JSON 45 | // without first passing through serializerr 46 | var poorlySerializedError = JSON.parse(JSON.stringify(error)) 47 | 48 | // oh dear: 49 | console.log(poorlySerializedError.name) // undefined 50 | console.log(poorlySerializedError.message) // undefined 51 | console.log(poorlySerializedError.stack) // undefined 52 | 53 | // bring forth the serializerr 54 | var errorObject = serializerr(error) 55 | 56 | var wellSerializedError = JSON.parse(JSON.stringify(errorObject)) 57 | 58 | // properties are conserved! 59 | console.log(wellSerializedError.name) // Error 60 | console.log(wellSerializedError.message) // an error occurred 61 | console.log(wellSerializedError.stack) // Error: an error occurred\n at Test. ... 62 | 63 | // note errorObject is a vanilla Object, not an Error 64 | errorObject instanceof Error // false 65 | ``` 66 | 67 | ## Why 68 | 69 | If you've ever tried to send an Error over a JSON-encoded connection 70 | you've probably been surprised to find all the useful information is 71 | sapped out of it; all the juicy properties like `name`, `message` & 72 | `stack` are non-enumerable thus they are not included in the 73 | stringified JSON. This may be non-standard behaviour, as I could not 74 | locate any mention in either the ES5.1 or the ES6 spec about it, but 75 | Error properties are non-enumerable both in V8 (Chrome/io.js/Node.js) & 76 | SpiderMonkey (Firefox). 77 | 78 | I believe Error property non-enumerability was added as a security 79 | measure to prevent stack traces and other sensitive information 80 | accidentally leaking, but it's not uncommon to actually want to send 81 | the data in Error objects over the wire. 82 | 83 | `serializerr` makes an Object suitable for serializing to & from 84 | JSON. Not restricted to use with Errors, will work with any Object. 85 | 86 | ## Notes on 'ize' vs 'ise' 87 | 88 | Name was selected as programming world is mostly Americanised, and npm 89 | search does not seem to do effective stemming. 90 | 91 | This decision came with strong feelings of guilt and shame about what I thought 92 | was blasphemous Americanised spelling, but it turns out this is a 93 | misconception thus I am pardoned: 94 | 95 | > The -ize spelling is often incorrectly seen as an Americanism in 96 | > Britain. However, the Oxford English Dictionary (OED) recommends -ize 97 | > and notes that the -ise spelling is from French. 98 | 99 | From [Wikipedia: American and British English spelling differences](http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences#-ise.2C_-ize_.28-isation.2C_-ization.29) 100 | 101 | ## License 102 | 103 | ISC 104 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import protochain from 'protochain' 2 | 3 | function serializerr (obj = {}) { 4 | const chain = protochain(obj) 5 | .filter(obj => obj !== Object.prototype) 6 | return [obj] 7 | .concat(chain) 8 | .map(item => Object.getOwnPropertyNames(item)) 9 | .reduce((result, names) => { 10 | names.forEach(name => { 11 | result[name] = obj[name] 12 | }) 13 | return result 14 | }, {}) 15 | } 16 | 17 | module.exports = serializerr 18 | serializerr.serializerr = serializerr 19 | export default serializerr 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serializerr", 3 | "version": "1.0.3", 4 | "description": "Convert Errors & Objects into an easily-serialized vanilla Object.", 5 | "main": "serializerr.js", 6 | "scripts": { 7 | "test": "(babel test.js > serializerr-test.js) && tape serializerr-test.js && standard", 8 | "prepublish": "babel index.js > serializerr.js", 9 | "pretest": "npm run prepublish", 10 | "posttest": "rm serializerr-test.js" 11 | }, 12 | "keywords": [ 13 | "object", 14 | "error", 15 | "utility", 16 | "JSON", 17 | "serialise", 18 | "errors", 19 | "non-enumerable", 20 | "enumberable", 21 | "stringify", 22 | "properties" 23 | ], 24 | "babel": { 25 | "presets": [ 26 | "es2015" 27 | ] 28 | }, 29 | "author": "Tim Oxley ", 30 | "license": "ISC", 31 | "devDependencies": { 32 | "babel-cli": "^6.11.4", 33 | "babel-preset-es2015": "^6.13.2", 34 | "standard": "^7.1.2", 35 | "tape": "^4.6.0" 36 | }, 37 | "standard": { 38 | "ignore": [ 39 | "serializerr.js", 40 | "serializerr-test.js" 41 | ] 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/timoxley/serializerr.git" 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/timoxley/serializerr/issues" 49 | }, 50 | "homepage": "https://github.com/timoxley/serializerr", 51 | "dependencies": { 52 | "protochain": "^1.0.5" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /serializerr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _protochain = require('protochain'); 8 | 9 | var _protochain2 = _interopRequireDefault(_protochain); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | function serializerr() { 14 | var obj = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 15 | 16 | var chain = (0, _protochain2.default)(obj).filter(function (obj) { 17 | return obj !== Object.prototype; 18 | }); 19 | return [obj].concat(chain).map(function (item) { 20 | return Object.getOwnPropertyNames(item); 21 | }).reduce(function (result, names) { 22 | names.forEach(function (name) { 23 | result[name] = obj[name]; 24 | }); 25 | return result; 26 | }, {}); 27 | } 28 | 29 | module.exports = serializerr; 30 | serializerr.serializerr = serializerr; 31 | exports.default = serializerr; 32 | 33 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'tape' 4 | import serializerr from './' 5 | 6 | test('with Errors', t => { 7 | const err = new Error('message') 8 | t.ok(err.stack, 'has stack') 9 | const errObj = serializerr(err) 10 | const jsonError = JSONify(errObj) 11 | t.ok(jsonError.name, 'has name') 12 | t.ok(jsonError.message, 'has message') 13 | t.ok(jsonError.stack, 'has stack') 14 | t.end() 15 | }) 16 | 17 | test('regular objects', t => { 18 | const obj = {ok: {}} 19 | const result = serializerr(obj) 20 | t.deepEqual(obj, result) 21 | t.equal(result.ok, obj.ok) 22 | t.end() 23 | }) 24 | 25 | test('object with no prototype', t => { 26 | const obj = Object.create(null) 27 | obj.ok = {} 28 | const result = serializerr(obj) 29 | t.deepEqual(obj, result) 30 | t.equal(result.ok, obj.ok) 31 | t.end() 32 | }) 33 | 34 | test('example', t => { 35 | const error = new Error('an error occurred') 36 | 37 | t.test('without serializerr', t => { 38 | const poorlySerializedError = JSON.parse(JSON.stringify(error)) 39 | t.equal(poorlySerializedError.name, undefined, 'name is undefined') 40 | t.equal(poorlySerializedError.message, undefined, 'message is undefined') 41 | t.equal(poorlySerializedError.stack, undefined, 'stack is undefined') 42 | t.end() 43 | }) 44 | 45 | t.test('with serializerr', t => { 46 | const errorObject = serializerr(error) 47 | const wellSerializedError = JSON.parse(JSON.stringify(errorObject)) 48 | t.equal(wellSerializedError.name, error.name, 'has correct name') 49 | t.equal(wellSerializedError.message, error.message, 'has correct message') 50 | t.equal(wellSerializedError.stack, error.stack, 'has correct stack') 51 | t.end() 52 | }) 53 | t.end() 54 | }) 55 | 56 | function JSONify (item) { 57 | return JSON.parse(JSON.stringify(item)) 58 | } 59 | --------------------------------------------------------------------------------