Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

json-stringify-extended

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

json-stringify-extended - npm Package Compare versions

Comparing version
2.0.0
to
2.1.0
+293
src/main.js
'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

[![JS Standard Style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
[![Build Status](https://travis-ci.org/braceslab/json-stringify-extended.svg?branch=master)](https://travis-ci.org/braceslab/json-stringify-extended)
![100% code coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)
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

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