json-stringify-extended
Advanced tools
+293
| 'use strict' | ||
| const uglify = require('uglify-es') | ||
| const FUNCTION_COMPRESS_OPTIONS = { parse: { bare_returns: true } } | ||
| const FUNCTION_COMPRESS_NAMED = 'const f=' | ||
| const FUNCTION_COMPRESS_NAMED_LENGTH = FUNCTION_COMPRESS_NAMED.length | ||
| // regexp collection | ||
| const SYMBOL_STRIP = /Symbol\((.*)\)/ | ||
| const IRREGULAR_KEY = /^[^a-zA-Z]/ | ||
| const SQUARED_IN_KEY = /^\w[\d\w_]*$/ | ||
| const STRIP_TRAILING_SEMICOLON = /;+$/ | ||
| /** | ||
| * | ||
| * @param {*} data | ||
| * @param {Boolean} options.safe works in safe mode, so will not throws exception for circularity; default false | ||
| * @param {String} options.endline end line with; default '\n' | ||
| * @param {String} options.spacing indentation string; default ' ' (two spaces) | ||
| * @param {String} options.keyQuote character used for quote keys; default null - no quotes | ||
| * @param {String} options.valueQuote character used for quote values; default " | ||
| * @param {Boolean} options.keySpace add space after key: ; default false | ||
| * @param {function(key:String, value:*)} options.replace replace by key or value | ||
| * @param {function(key:String, value:*)} options.filter filter by key or value | ||
| * @param {Boolean} options.discard discard null and undefined values; default false | ||
| * @param {Boolean} options.compress compress data like function, Date, Buffer; default false | ||
| */ | ||
| const stringify = function (data, options) { | ||
| const _done = { | ||
| values: [], | ||
| paths: [] | ||
| } | ||
| const _counter = { | ||
| object: 0, | ||
| array: 0 | ||
| } | ||
| let _keySpace | ||
| function validate (options) { | ||
| if (!options) { | ||
| // default options | ||
| options = { | ||
| endline: '\n', | ||
| spacing: ' ', | ||
| keyQuote: null, | ||
| keySpace: false, | ||
| valueQuote: '"', | ||
| safe: false, | ||
| replace: null, | ||
| filter: null, | ||
| discard: false, | ||
| compress: false | ||
| } | ||
| } else { | ||
| if (!options.endline && options.endline !== '') { | ||
| options.endline = '\n' | ||
| } | ||
| if (!options.spacing && options.spacing !== '') { | ||
| options.spacing = ' ' | ||
| } | ||
| if (!options.valueQuote) { | ||
| options.valueQuote = '"' | ||
| } | ||
| if (options.replace !== undefined && typeof options.replace !== 'function') { | ||
| throw new Error('options.replace is not a function') | ||
| } | ||
| if (options.filter !== undefined && typeof options.filter !== 'function') { | ||
| throw new Error('options.filter is not a function') | ||
| } | ||
| } | ||
| _keySpace = options.keySpace ? ' ' : '' | ||
| return options | ||
| } | ||
| function replace (str, find, replace) { | ||
| if (str.indexOf(find) === -1) { | ||
| return str | ||
| } | ||
| return str.split(find).join(replace) | ||
| } | ||
| function circularity (val, path) { | ||
| const i = _done.values.indexOf(val) | ||
| if (i !== -1 && path.indexOf(_done.paths[i]) === 0) { | ||
| if (!options.safe) { | ||
| throw new Error('Circular reference @ ' + path) | ||
| } | ||
| return true | ||
| } | ||
| _done.values.push(val) | ||
| _done.paths.push(path) | ||
| return false | ||
| } | ||
| const _serialize = { | ||
| function: function (obj) { | ||
| if (options.compress) { | ||
| let _min | ||
| try { | ||
| _min = uglify.minify(obj.toString(), FUNCTION_COMPRESS_OPTIONS) | ||
| if (!_min.code) { | ||
| _min = uglify.minify(FUNCTION_COMPRESS_NAMED + obj.toString(), FUNCTION_COMPRESS_OPTIONS) | ||
| _min.code = _min.code.substr(FUNCTION_COMPRESS_NAMED_LENGTH) | ||
| } | ||
| const _code = _min.code// ? || obj.toString() | ||
| return _code.replace(STRIP_TRAILING_SEMICOLON, '') | ||
| } catch (error) { | ||
| console.warn('unable to compress function', obj.toString(), _min.error) | ||
| return obj.toString() | ||
| } | ||
| } | ||
| return obj.toString() | ||
| }, | ||
| number: function (n) { | ||
| return n | ||
| }, | ||
| string: function (str) { | ||
| if (str.indexOf('\n') !== -1) { | ||
| str = str.split('\n').join('\\n') | ||
| } | ||
| if (str.indexOf('\t') !== -1) { | ||
| str = str.split('\t').join('\\t') | ||
| } | ||
| return quote(str, options.valueQuote) | ||
| }, | ||
| boolean: function (value) { | ||
| return value ? 'true' : 'false' | ||
| }, | ||
| null: function () { | ||
| return 'null' | ||
| }, | ||
| undefined: function () { | ||
| return 'undefined' | ||
| }, | ||
| deferred: function (val) { | ||
| return val.toString() | ||
| }, | ||
| date: function (date) { | ||
| if (options.compress) { | ||
| return 'new Date(' + date.getTime() + ')' | ||
| } | ||
| return 'new Date(' + options.valueQuote + date.toISOString() + options.valueQuote + ')' | ||
| }, | ||
| symbol: function (symbol) { | ||
| return 'Symbol(' + options.valueQuote + symbol.toString().match(SYMBOL_STRIP)[1] + options.valueQuote + ')' | ||
| }, | ||
| regexp: function (obj) { | ||
| return obj.toString() | ||
| }, | ||
| buffer: function (obj) { | ||
| return 'Buffer.from(' + options.valueQuote + obj.toString('base64') + options.valueQuote + ')' | ||
| }, | ||
| object: function (obj, deep, path) { | ||
| _counter.object++ | ||
| if (!path) { | ||
| path = '{}' | ||
| } | ||
| const _spacing0 = spacing(deep) | ||
| const _spacing1 = _spacing0 + options.spacing | ||
| if (circularity(obj, path)) { | ||
| return options.endline + _spacing1 + '[Circularity]' + options.endline + _spacing0 | ||
| } | ||
| const _out = [] | ||
| for (const key in obj) { | ||
| const _path = path + '.' + key | ||
| const _item = item(key, obj[key], deep + 1, _path) | ||
| // if item is discarded by filtering | ||
| if (!_item) { | ||
| continue | ||
| } | ||
| // wrap strange key with quotes | ||
| if (_item.key.match(IRREGULAR_KEY) || !_item.key.match(SQUARED_IN_KEY)) { | ||
| _item.key = quote(key, options.keyQuote || '"') | ||
| } | ||
| _out.push(options.endline + _spacing1 + _item.key + ':' + _keySpace + _item.value) | ||
| } | ||
| return '{' + _out.join(',') + options.endline + _spacing0 + '}' | ||
| }, | ||
| array: function (array, deep, path) { | ||
| _counter.array++ | ||
| if (!path) { | ||
| path = '[]' | ||
| } | ||
| if (circularity(array, path)) { | ||
| return '[Circularity]' | ||
| } | ||
| const _spacing0 = spacing(deep) | ||
| const _spacing1 = _spacing0 + options.spacing | ||
| const _out = [] | ||
| for (let i = 0; i < array.length; i++) { | ||
| const _path = path + '.' + i | ||
| const _item = item(null, array[i], deep + 1, _path) | ||
| if (_item) { | ||
| _out.push(options.endline + _spacing1 + _item.value) | ||
| } | ||
| } | ||
| return '[' + _out.join(',') + options.endline + _spacing0 + ']' | ||
| } | ||
| } | ||
| function spacing (deep) { | ||
| let spacing = '' | ||
| for (let i = 0; i < deep - 1; i++) { | ||
| spacing += options.spacing | ||
| } | ||
| return spacing | ||
| } | ||
| function quote (value, quote) { | ||
| return quote + replace(value, quote, '\\' + quote) + quote | ||
| } | ||
| function item (key, value, deep, path) { | ||
| if (!deep) deep = 1 | ||
| if ((options.discard) && (value === undefined || value === null)) { | ||
| return null | ||
| } | ||
| if (options.filter && !options.filter(key, value)) { | ||
| return null | ||
| } | ||
| if (options.replace) { | ||
| ({ key, value } = options.replace(key, value)) | ||
| } | ||
| let _type = typeof value | ||
| if (_type === 'object') { | ||
| if (value instanceof Array) { | ||
| _type = 'array' | ||
| } else if (value instanceof Date) { | ||
| _type = 'date' | ||
| } else if (value instanceof RegExp) { | ||
| _type = 'regexp' | ||
| } else if (value instanceof Buffer) { | ||
| _type = 'buffer' | ||
| } else if (value instanceof stringify._deferred) { | ||
| _type = 'deferred' | ||
| } else if (value === null) { | ||
| _type = 'null' | ||
| } | ||
| } | ||
| return { key, value: _serialize[_type](value, deep, path) } | ||
| } | ||
| options = validate(options) | ||
| const _item = item(null, data) | ||
| return _item.value | ||
| } | ||
| // deferred type | ||
| stringify.deferred = function (val) { | ||
| this.val = val | ||
| return new stringify._deferred(val) | ||
| } | ||
| stringify._deferred = function (val) { | ||
| this.val = val | ||
| } | ||
| stringify._deferred.prototype.toString = function () { | ||
| return this.val | ||
| } | ||
| // preset options | ||
| stringify.options = { | ||
| json: { | ||
| keyQuote: '"', | ||
| keySpace: true | ||
| }, | ||
| standardjs: { | ||
| keySpace: true, | ||
| valueQuote: "'" | ||
| }, | ||
| compact: { | ||
| valueQuote: "'", | ||
| endline: '', | ||
| spacing: '' | ||
| } | ||
| } | ||
| module.exports = stringify |
+14
-26
| { | ||
| "name": "json-stringify-extended", | ||
| "version": "2.0.0", | ||
| "version": "2.1.0", | ||
| "dependencies": { | ||
@@ -8,19 +8,8 @@ "uglify-es": "^3.3.9" | ||
| "devDependencies": { | ||
| "babel-cli": "^6.x", | ||
| "babel-core": "^6.x", | ||
| "babel-preset-env": "^1.6.x", | ||
| "babelify": "^8.x", | ||
| "browserify": "^14.x", | ||
| "fs-extra": "^5.x", | ||
| "gulp": "^3.x", | ||
| "gulp-buffer": "0.0.x", | ||
| "gulp-clean": "^0.3.x", | ||
| "gulp-sourcemaps": "^2.6.x", | ||
| "microbundle": "^0.4.4", | ||
| "pre-commit": "^1.x", | ||
| "standard": "^11.x", | ||
| "tap": "^11.x", | ||
| "vinyl-source-stream": "^1.x" | ||
| "pre-commit": "^1.2.2", | ||
| "standard": "^16.0.3", | ||
| "tap": "^14.11.0" | ||
| }, | ||
| "pre-commit": [ | ||
| "format", | ||
| "test" | ||
@@ -30,19 +19,18 @@ ], | ||
| "ignore": [ | ||
| "test/", | ||
| "dist/" | ||
| "dist/", | ||
| "test/" | ||
| ] | ||
| }, | ||
| "scripts": { | ||
| "build": "gulp", | ||
| "clean": "gulp clean", | ||
| "test": "standard && tap test/*", | ||
| "test-cov": "standard && tap test/* --cov" | ||
| "format": "standard --fix", | ||
| "test": "tap test/*", | ||
| "test:coverage": "tap test/* --cov" | ||
| }, | ||
| "description": "JSON.stringify with extended data types support", | ||
| "main": "main.js", | ||
| "main": "src/main.js", | ||
| "files": [ | ||
| "main.js" | ||
| "src/main.js" | ||
| ], | ||
| "engines": { | ||
| "node": ">= 6.x" | ||
| "node": ">= 10" | ||
| }, | ||
@@ -53,3 +41,3 @@ "author": "Simone Sanfratello <simone@braceslab.com>", | ||
| "type": "git", | ||
| "url": "git+https://github.com/braceslab/json-stringify-extended.git" | ||
| "url": "git+https://github.com/simone-sanfratello/json-stringify-extended.git" | ||
| }, | ||
@@ -56,0 +44,0 @@ "keywords": [ |
+14
-16
@@ -6,4 +6,5 @@ # json-stringify-extended | ||
| [](http://standardjs.com/) | ||
| [](https://travis-ci.org/braceslab/json-stringify-extended) | ||
|  | ||
| JSON.stringify any data types | ||
@@ -13,7 +14,6 @@ | ||
| - stringify more primitive types into JSON format | ||
| - stringify in JavaScript Object | ||
| - Use not yet defined types (e.g. enums) | ||
| - Avoid useless quotes | ||
| - Choose to throw exception, or not in circularity references | ||
| - stringify primitive js types properly without limitation | ||
| - get control on circular reference | ||
| - use custom types not yet defined (e.g. enums) | ||
| - avoid quotes if not needed | ||
@@ -89,2 +89,3 @@ ## Installing | ||
| * ``Buffer`` | ||
| * ``Symbol`` | ||
| * ``null`` | ||
@@ -255,3 +256,3 @@ * ``Infinity`` | ||
| Discard the values `null` and ``undefined``. | ||
| Discard values `null` and ``undefined``. | ||
@@ -262,2 +263,7 @@ --- | ||
| #### v. 2.1.0 | ||
| - support `Symbol` types | ||
| - **100%** code coverage | ||
| - drop `node` < `10` | ||
| #### v. 2.0.0 | ||
@@ -268,10 +274,2 @@ - separate `compress` and `discard` option (to keep object keys on compress) | ||
| ## TODO | ||
| - [ ] output compatibility: es5, es6 | ||
| - [ ] coverage badge | ||
| - [ ] browser support | ||
| - (see)[https://www.contentful.com/blog/2017/04/04/es6-modules-support-lands-in-browsers-is-it-time-to-rethink-bundling/] | ||
| --- | ||
| ## License | ||
@@ -281,3 +279,3 @@ | ||
| Copyright (c) 2017-2018 [braces lab](https://braceslab.com) | ||
| Copyright (c) 2017-2020 Simone Sanfratello | ||
@@ -284,0 +282,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
-285
| const uglify = require('uglify-es') | ||
| const FUNCTION_COMPRESS_OPTIONS = {parse: {bare_returns: true}} | ||
| const FUNCTION_COMPRESS_NAMED = 'const f=' | ||
| const FUNCTION_COMPRESS_NAMED_LENGTH = FUNCTION_COMPRESS_NAMED.length | ||
| /** | ||
| * | ||
| * @param {*} data | ||
| * @param {Boolean} options.safe works in safe mode, so will not throws exception for circularity; default false | ||
| * @param {String} options.endline end line with; default '\n' | ||
| * @param {String} options.spacing indentation string; default ' ' (two spaces) | ||
| * @param {String} options.keyQuote character used for quote keys; default null - no quotes | ||
| * @param {String} options.valueQuote character used for quote values; default " | ||
| * @param {Boolean} options.keySpace add space after key: ; default false | ||
| * @param {function(key:String, value:*)} options.replace replace by key or value | ||
| * @param {function(key:String, value:*)} options.filter filter by key or value | ||
| * @param {Boolean} options.discard discard null and undefined values; default false | ||
| * @param {Boolean} options.compress compress data like function, Date, Buffer; default false | ||
| */ | ||
| const stringify = function (data, options) { | ||
| const __done = { | ||
| values: [], | ||
| paths: [] | ||
| } | ||
| const __counter = { | ||
| object: 0, | ||
| array: 0 | ||
| } | ||
| let __keySpace | ||
| const __options = function () { | ||
| if (!options) { | ||
| // default options | ||
| options = { | ||
| endline: '\n', | ||
| spacing: ' ', | ||
| keyQuote: null, | ||
| keySpace: false, | ||
| valueQuote: '"', | ||
| safe: false, | ||
| replace: null, | ||
| filter: null, | ||
| discard: false, | ||
| compress: false | ||
| } | ||
| } else { | ||
| if (!options.endline && options.endline !== '') { | ||
| options.endline = '\n' | ||
| } | ||
| if (!options.spacing && options.spacing !== '') { | ||
| options.spacing = ' ' | ||
| } | ||
| if (!options.valueQuote) { | ||
| options.valueQuote = '"' | ||
| } | ||
| if (options.replace !== undefined && typeof options.replace !== 'function') { | ||
| throw new Error('options.replace is not a function') | ||
| } | ||
| if (options.filter !== undefined && typeof options.filter !== 'function') { | ||
| throw new Error('options.filter is not a function') | ||
| } | ||
| } | ||
| __keySpace = options.keySpace ? ' ' : '' | ||
| } | ||
| const __replace = function (str, find, replace) { | ||
| if (str.indexOf(find) === -1) { | ||
| return str | ||
| } | ||
| return str.split(find).join(replace) | ||
| } | ||
| const __circularity = function (val, path) { | ||
| const i = __done.values.indexOf(val) | ||
| if (i !== -1 && path.indexOf(__done.paths[i]) === 0) { | ||
| if (!options.safe) { | ||
| throw new Error('Circular reference @ ' + path) | ||
| } | ||
| return true | ||
| } | ||
| __done.values.push(val) | ||
| __done.paths.push(path) | ||
| return false | ||
| } | ||
| const __serialize = { | ||
| function: function (obj) { | ||
| if (options.compress) { | ||
| let _min | ||
| try { | ||
| _min = uglify.minify(obj.toString(), FUNCTION_COMPRESS_OPTIONS) | ||
| if (!_min.code) { | ||
| _min = uglify.minify(FUNCTION_COMPRESS_NAMED + obj.toString(), FUNCTION_COMPRESS_OPTIONS) | ||
| _min.code = _min.code.substr(FUNCTION_COMPRESS_NAMED_LENGTH) | ||
| } | ||
| const _code = _min.code || obj.toString() | ||
| return _code.replace(/;+$/, '') | ||
| } catch (e) { | ||
| console.warn('unable to compress function', obj.toString(), _min.error) | ||
| return obj.toString() | ||
| } | ||
| } | ||
| return obj.toString() | ||
| }, | ||
| number: function (obj) { | ||
| return obj | ||
| }, | ||
| string: function (obj) { | ||
| if (obj.indexOf('\n') !== -1) { | ||
| obj = obj.split('\n').join('\\n') | ||
| } | ||
| if (obj.indexOf('\t') !== -1) { | ||
| obj = obj.split('\t').join('\\t') | ||
| } | ||
| return __quote(obj, options.valueQuote) | ||
| }, | ||
| boolean: function (obj) { | ||
| return obj ? 'true' : 'false' | ||
| }, | ||
| null: function () { | ||
| return 'null' | ||
| }, | ||
| undefined: function () { | ||
| return 'undefined' | ||
| }, | ||
| deferred: function (obj) { | ||
| return obj.toString() | ||
| }, | ||
| date: function (obj) { | ||
| if (options.compress) { | ||
| return 'new Date(' + obj.getTime() + ')' | ||
| } | ||
| return 'new Date(' + options.valueQuote + obj.toISOString() + options.valueQuote + ')' | ||
| }, | ||
| regexp: function (obj) { | ||
| return obj.toString() | ||
| }, | ||
| buffer: function (obj) { | ||
| // @todo check nodejs version? | ||
| return 'Buffer.from(' + options.valueQuote + obj.toString('base64') + options.valueQuote + ')' | ||
| }, | ||
| object: function (obj, deep, path) { | ||
| __counter.object++ | ||
| if (!path) { | ||
| path = '{}' | ||
| } | ||
| const _spacing0 = __spacing(deep) | ||
| const _spacing1 = _spacing0 + options.spacing | ||
| if (__circularity(obj, path)) { | ||
| return options.endline + _spacing1 + '[Circularity]' + options.endline + _spacing0 | ||
| } | ||
| const _out = [] | ||
| for (const key in obj) { | ||
| const _path = path + '.' + key | ||
| const _item = __item(key, obj[key], deep + 1, _path) | ||
| // if item is discarded by filtering | ||
| if (!_item) { | ||
| continue | ||
| } | ||
| // wrap strange key with quotes | ||
| if (_item.key) { | ||
| if (_item.key.match(/^[^a-zA-Z]/) || !_item.key.match(/^\w[\d\w_]*$/)) { | ||
| _item.key = __quote(key, options.keyQuote || '"') | ||
| } | ||
| } | ||
| _out.push(options.endline + _spacing1 + _item.key + ':' + __keySpace + _item.value) | ||
| } | ||
| return '{' + _out.join(',') + options.endline + _spacing0 + '}' | ||
| }, | ||
| array: function (array, deep, path) { | ||
| __counter.array++ | ||
| if (!path) { | ||
| path = '[]' | ||
| } | ||
| if (__circularity(array, path)) { | ||
| return '[Circularity]' | ||
| } | ||
| const _spacing0 = __spacing(deep) | ||
| const _spacing1 = _spacing0 + options.spacing | ||
| const _out = [] | ||
| for (let i = 0; i < array.length; i++) { | ||
| const _path = path + '.' + i | ||
| const _item = __item(null, array[i], deep + 1, _path) | ||
| if (_item) { | ||
| _out.push(options.endline + _spacing1 + _item.value) | ||
| } | ||
| } | ||
| return '[' + _out.join(',') + options.endline + _spacing0 + ']' | ||
| } | ||
| } | ||
| const __spacing = function (deep) { | ||
| let _spacing = '' | ||
| for (let i = 0; i < deep - 1; i++) { | ||
| _spacing += options.spacing | ||
| } | ||
| return _spacing | ||
| } | ||
| const __quote = function (value, quote) { | ||
| return quote + __replace(value, quote, '\\' + quote) + quote | ||
| } | ||
| const __item = function (key, value, deep, path) { | ||
| if (!deep) deep = 1 | ||
| if ((options.discard) && (value === undefined || value === null)) { | ||
| return null | ||
| } | ||
| if (options.filter && !options.filter(key, value)) { | ||
| return null | ||
| } | ||
| if (options.replace) { | ||
| ({key, value} = options.replace(key, value)) | ||
| } | ||
| let _type = typeof value | ||
| if (_type === 'object') { | ||
| if (value instanceof Array) { | ||
| _type = 'array' | ||
| } else if (value instanceof Date) { | ||
| _type = 'date' | ||
| } else if (value instanceof RegExp) { | ||
| _type = 'regexp' | ||
| } else if (value instanceof Buffer) { | ||
| _type = 'buffer' | ||
| } else if (value instanceof stringify._deferred) { | ||
| _type = 'deferred' | ||
| } else if (value === null) { | ||
| _type = 'null' | ||
| } | ||
| } | ||
| return {key, value: __serialize[_type](value, deep, path)} | ||
| } | ||
| __options() | ||
| const _item = __item(null, data) | ||
| return _item ? _item.value : {} | ||
| } | ||
| // deferred type | ||
| stringify.deferred = function (val) { | ||
| this.val = val | ||
| return new stringify._deferred(val) | ||
| } | ||
| stringify._deferred = function (val) { | ||
| this.val = val | ||
| } | ||
| stringify._deferred.prototype.toString = function () { | ||
| return this.val | ||
| } | ||
| // prepared options | ||
| stringify.options = { | ||
| json: { | ||
| keyQuote: '"', | ||
| keySpace: true | ||
| }, | ||
| standardjs: { | ||
| keySpace: true, | ||
| valueQuote: "'" | ||
| }, | ||
| compact: { | ||
| valueQuote: "'", | ||
| endline: '', | ||
| spacing: '' | ||
| } | ||
| } | ||
| module.exports = stringify |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
3
-80%263
2.73%15249
-2.21%292
-0.68%1
Infinity%