├── .eslintignore ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── .zuul.yml ├── LICENSE ├── Makefile ├── README.md ├── package.json ├── src ├── index.js └── internal │ ├── reference.js │ └── utils.js └── test └── index.test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | tmp 3 | lib 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [14.x, 16.x, 17.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | # cache: 'npm' 29 | - run: npm install --ignore-scripts 30 | - run: npm run ci 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage/ 3 | .nyc_output/ 4 | doc/ 5 | tmp/ 6 | lib/ 7 | *.log 8 | *.tgz 9 | *.sh 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: mocha-bdd 2 | # tunnel_host: http://focusaurus.com 3 | browsers: 4 | - name: chrome 5 | platform: Windows 10 6 | version: 7 | - 69 8 | - latest 9 | - name: firefox 10 | platform: Windows 10 11 | version: 12 | - 60 13 | - latest 14 | - name: safari 15 | version: 16 | - 11 17 | - latest 18 | - name: MicrosoftEdge 19 | version: 20 | - 17 21 | - latest 22 | # - name: iphone 23 | # version: latest 24 | # Not Supported 25 | # - name: internet explorer 26 | # version: 11 27 | # platform: Windows 10 28 | # - name: internet explorer 29 | # version: 9..latest 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present commenthol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: readme v8 v10 v12 v13 2 | 3 | readme: README.md 4 | markedpp --githubid -i $< -o $< 5 | 6 | v%: 7 | n $@ && npm test 8 | 9 | zuul: 10 | node_modules/.bin/zuul --local 3000 test/*.js 11 | 12 | .PHONY: all readme zuul 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serialize-to-js 2 | 3 | > serialize objects to javascript 4 | 5 | [![NPM version](https://badge.fury.io/js/serialize-to-js.svg)](https://www.npmjs.com/package/serialize-to-js/) 6 | [![Build Status](https://github.com/commenthol/serialize-to-js/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/commenthol/serialize-to-js/actions/workflows/ci.yml?query=branch%3Amaster) 7 | 8 | 9 | Serialize objects into a string while checking circular structures and respecting references. 10 | 11 | The following Objects are supported 12 | 13 | - String 14 | - Number 15 | - Boolean 16 | - Object 17 | - Array 18 | - RegExp 19 | - Error 20 | - Date 21 | - Buffer 22 | - Int8Array, Uint8Array, Uint8ClampedArray 23 | - Int16Array, Uint16Array 24 | - Int32Array, Uint32Array, Float32Array 25 | - Float64Array 26 | - Set 27 | - Map 28 | 29 | ## Table of Contents 30 | 31 | 32 | 33 | * [Methods](#methods) 34 | * [serialize](#serialize) 35 | * [Contribution and License Agreement](#contribution-and-license-agreement) 36 | * [License](#license) 37 | 38 | 39 | 40 | ## Methods 41 | 42 | ### serialize 43 | 44 | `serialize(source, opts, opts.ignoreCircular, opts.reference)` 45 | 46 | serializes an object to javascript 47 | 48 | #### Example - serializing regex, date, buffer, ... 49 | 50 | ```js 51 | const serialize = require('serialize-to-js') 52 | const obj = { 53 | str: '', 54 | num: 3.1415, 55 | bool: true, 56 | nil: null, 57 | undef: undefined, 58 | obj: { foo: 'bar' }, 59 | arr: [1, '2'], 60 | regexp: /^test?$/, 61 | date: new Date(), 62 | buffer: new Buffer('data'), 63 | set: new Set([1, 2, 3]), 64 | map: new Map([['a': 1],['b': 2]]) 65 | } 66 | console.log(serialize(obj)) 67 | //> '{str: "\u003Cscript\u003Evar a = 0 \u003E 1\u003C\u002Fscript\u003E", 68 | //> num: 3.1415, bool: true, nil: null, undef: undefined, 69 | //> obj: {foo: "bar"}, arr: [1, "2"], regexp: new RegExp("^test?$", ""), 70 | //> date: new Date("2019-12-29T10:37:36.613Z"), 71 | //> buffer: Buffer.from("ZGF0YQ==", "base64"), set: new Set([1, 2, 3]), 72 | //> map: new Map([["a", 1], ["b", 2]])}' 73 | ``` 74 | 75 | #### Example - serializing while respecting references 76 | 77 | ```js 78 | var serialize = require('serialize-to-js') 79 | var obj = { object: { regexp: /^test?$/ } }; 80 | obj.reference = obj.object; 81 | var opts = { reference: true }; 82 | console.log(serialize(obj, opts)); 83 | //> {object: {regexp: /^test?$/}} 84 | console.log(opts.references); 85 | //> [ [ '.reference', '.object' ] ] 86 | ``` 87 | 88 | **Parameters** 89 | 90 | **source**: `Object | Array | function | Any`, source to serialize 91 | **opts**: `Object`, options 92 | **opts.ignoreCircular**: `Boolean`, ignore circular objects 93 | **opts.reference**: `Boolean`, reference instead of a copy (requires post-processing of opts.references) 94 | **opts.unsafe**: `Boolean`, do not escape chars `<>/` 95 | **Returns**: `String`, serialized representation of `source` 96 | 97 | 98 | ## Contribution and License Agreement 99 | 100 | If you contribute code to this project, you are implicitly allowing your 101 | code to be distributed under the MIT license. You are also implicitly 102 | verifying that all code is your original work or correctly attributed 103 | with the source of its origin and licence. 104 | 105 | ## License 106 | 107 | Copyright (c) 2016- commenthol (MIT License) 108 | 109 | See [LICENSE][] for more info. 110 | 111 | [LICENSE]: ./LICENSE 112 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serialize-to-js", 3 | "version": "3.1.2", 4 | "description": "serialize objects to javascript", 5 | "keywords": [ 6 | "javascript", 7 | "objects", 8 | "serialize" 9 | ], 10 | "homepage": "https://github.com/commenthol/serialize-to-js", 11 | "bugs": { 12 | "url": "https://github.com/commenthol/serialize-to-js/issues" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/commenthol/serialize-to-js.git" 17 | }, 18 | "license": "MIT", 19 | "author": "commenthol ", 20 | "maintainers": "commenthol ", 21 | "main": "lib", 22 | "module": "src", 23 | "directories": { 24 | "lib": "lib", 25 | "test": "test" 26 | }, 27 | "files": [ 28 | "src", 29 | "lib" 30 | ], 31 | "scripts": { 32 | "build": "babel -d lib src", 33 | "ci": "npm run clean && npm run lint && npm run build && npm test", 34 | "clean": "rimraf lib doc coverage .nyc_output *.tgz", 35 | "coverage": "nyc -r text -r html npm test", 36 | "lint": "eslint src test", 37 | "prepublishOnly": "npm run ci", 38 | "readme": "markedpp --githubid -i README.md -o README.md", 39 | "test": "mocha" 40 | }, 41 | "babel": { 42 | "presets": [ 43 | "@babel/preset-env" 44 | ] 45 | }, 46 | "eslintConfig": { 47 | "env": { 48 | "mocha": true 49 | }, 50 | "plugins": [ 51 | "standard" 52 | ], 53 | "extends": "standard", 54 | "rules": { 55 | "key-spacing": 0, 56 | "no-console": 1 57 | } 58 | }, 59 | "mocha": { 60 | "check-leaks": true 61 | }, 62 | "dependencies": {}, 63 | "devDependencies": { 64 | "@babel/cli": "^7.17.6", 65 | "@babel/core": "^7.17.8", 66 | "@babel/preset-env": "^7.16.11", 67 | "eslint": "^7.32.0", 68 | "eslint-config-standard": "^14.1.1", 69 | "eslint-plugin-import": "^2.25.4", 70 | "eslint-plugin-node": "^11.1.0", 71 | "eslint-plugin-promise": "^4.3.1", 72 | "eslint-plugin-standard": "^4.1.0", 73 | "mocha": "^9.2.2", 74 | "nyc": "^15.1.0", 75 | "rimraf": "^3.0.2" 76 | }, 77 | "engines": { 78 | "node": ">=4.0.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @copyright 2016- commenthol 3 | * @license MIT 4 | */ 5 | 6 | 'use strict' 7 | 8 | // dependencies 9 | const utils = require('./internal/utils') 10 | const Ref = require('./internal/reference') 11 | 12 | /** 13 | * serializes an object to javascript 14 | * 15 | * @example serializing regex, date, buffer, ... 16 | * const serialize = require('serialize-to-js') 17 | * const obj = { 18 | * str: '', 19 | * num: 3.1415, 20 | * bool: true, 21 | * nil: null, 22 | * undef: undefined, 23 | * obj: { foo: 'bar' }, 24 | * arr: [1, '2'], 25 | * regexp: /^test?$/, 26 | * date: new Date(), 27 | * buffer: new Buffer('data'), 28 | * set: new Set([1, 2, 3]), 29 | * map: new Map([['a': 1],['b': 2]]) 30 | * } 31 | * console.log(serialize(obj)) 32 | * //> '{str: "\u003Cscript\u003Evar a = 0 \u003E 1\u003C\u002Fscript\u003E", 33 | * //> num: 3.1415, bool: true, nil: null, undef: undefined, 34 | * //> obj: {foo: "bar"}, arr: [1, "2"], regexp: new RegExp("^test?$", ""), 35 | * //> date: new Date("2019-12-29T10:37:36.613Z"), 36 | * //> buffer: Buffer.from("ZGF0YQ==", "base64"), set: new Set([1, 2, 3]), 37 | * //> map: new Map([["a", 1], ["b", 2]])}' 38 | * 39 | * @example serializing while respecting references 40 | * const serialize = require('serialize-to-js') 41 | * const obj = { object: { regexp: /^test?$/ } }; 42 | * obj.reference = obj.object; 43 | * const opts = { reference: true }; 44 | * console.log(serialize(obj, opts)); 45 | * //> {object: {regexp: /^test?$/}} 46 | * console.log(opts.references); 47 | * //> [ [ '.reference', '.object' ] ] 48 | * 49 | * @param {Object|Array|Function|Any} source - source to serialize 50 | * @param {Object} [opts] - options 51 | * @param {Boolean} opts.ignoreCircular - ignore circular objects 52 | * @param {Boolean} opts.reference - reference instead of a copy (requires post-processing of opts.references) 53 | * @param {Boolean} opts.unsafe - do not escape chars `<>/` 54 | * @return {String} serialized representation of `source` 55 | */ 56 | function serialize (source, opts = {}) { 57 | opts = opts || {} 58 | 59 | const visited = new Set() 60 | opts.references = [] 61 | const refs = new Ref(opts.references, opts) 62 | 63 | function stringify (source, opts) { 64 | const type = utils.toType(source) 65 | 66 | if (visited.has(source)) { 67 | if (opts.ignoreCircular) { 68 | switch (type) { 69 | case 'Array': 70 | return '[/*[Circular]*/]' 71 | case 'Object': 72 | return '{/*[Circular]*/}' 73 | default: 74 | return 'undefined /*[Circular]*/' 75 | } 76 | } else { 77 | throw new Error('can not convert circular structures.') 78 | } 79 | } 80 | 81 | switch (type) { 82 | case 'Null': 83 | return 'null' 84 | case 'String': 85 | return utils.quote(source, opts) || '""' 86 | case 'Function': { 87 | const _tmp = source.toString() 88 | const tmp = opts.unsafe ? _tmp : utils.saferFunctionString(_tmp, opts) 89 | // append function to es6 function within obj 90 | return !/^\s*(function|\([^)]*?\)\s*=>)/m.test(tmp) ? 'function ' + tmp : tmp 91 | } 92 | case 'RegExp': 93 | return `new RegExp(${utils.quote(source.source, opts)}, "${source.flags}")` 94 | case 'Date': 95 | if (utils.isInvalidDate(source)) return 'new Date("Invalid Date")' 96 | return `new Date(${utils.quote(source.toJSON(), opts)})` 97 | case 'Error': 98 | return `new Error(${utils.quote(source.message, opts)})` 99 | case 'Buffer': 100 | return `Buffer.from("${source.toString('base64')}", "base64")` 101 | case 'Array': { 102 | visited.add(source) 103 | const tmp = source.map(item => stringify(item, opts)) 104 | visited.delete(source) 105 | return `[${tmp.join(', ')}]` 106 | } 107 | case 'Int8Array': 108 | case 'Uint8Array': 109 | case 'Uint8ClampedArray': 110 | case 'Int16Array': 111 | case 'Uint16Array': 112 | case 'Int32Array': 113 | case 'Uint32Array': 114 | case 'Float32Array': 115 | case 'Float64Array': { 116 | const tmp = [] 117 | for (let i = 0; i < source.length; i++) { 118 | tmp.push(source[i]) 119 | } 120 | return `new ${type}([${tmp.join(', ')}])` 121 | } 122 | case 'Set': { 123 | visited.add(source) 124 | const tmp = Array.from(source).map(item => stringify(item, opts)) 125 | visited.delete(source) 126 | return `new ${type}([${tmp.join(', ')}])` 127 | } 128 | case 'Map': { 129 | visited.add(source) 130 | const tmp = Array.from(source).map(([key, value]) => `[${stringify(key, opts)}, ${stringify(value, opts)}]`) 131 | visited.delete(source) 132 | return `new ${type}([${tmp.join(', ')}])` 133 | } 134 | case 'Object': { 135 | visited.add(source) 136 | const tmp = [] 137 | for (const key in source) { 138 | if (Object.prototype.hasOwnProperty.call(source, key)) { 139 | if (opts.reference && utils.isObject(source[key])) { 140 | refs.push(key) 141 | if (!refs.hasReference(source[key])) { 142 | tmp.push(Ref.wrapkey(key, opts) + ': ' + stringify(source[key], opts)) 143 | } 144 | refs.pop() 145 | } else { 146 | tmp.push(Ref.wrapkey(key, opts) + ': ' + stringify(source[key], opts)) 147 | } 148 | } 149 | } 150 | visited.delete(source) 151 | return `{${tmp.join(', ')}}` 152 | } 153 | default: 154 | return '' + source 155 | } 156 | } 157 | 158 | return stringify(source, opts) 159 | } 160 | 161 | module.exports = serialize 162 | -------------------------------------------------------------------------------- /src/internal/reference.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @copyright 2015- commenthol 3 | * @license MIT 4 | */ 5 | 6 | 'use strict' 7 | 8 | const utils = require('./utils') 9 | 10 | const KEY = /^[a-zA-Z$_][a-zA-Z$_0-9]*$/ 11 | 12 | /** 13 | * handle references 14 | * @constructor 15 | * @param {Object} references 16 | * @param {boolean} opts.unsafe 17 | */ 18 | function Ref (references, opts) { 19 | this.keys = [] 20 | this.refs = [] 21 | this.key = [] 22 | this.references = references || [] 23 | this._opts = opts || {} 24 | } 25 | 26 | /** 27 | * wrap an object key 28 | * @api private 29 | * @param {String} key - objects key 30 | * @return {String} wrapped key in quotes if necessary 31 | */ 32 | Ref.wrapkey = function (key, opts) { 33 | return (KEY.test(key) ? key : utils.quote(key, opts)) 34 | } 35 | 36 | Ref.prototype = { 37 | /** 38 | * push `key` to interal array 39 | * @param {String} key 40 | */ 41 | push: function (key) { 42 | this.key.push(key) 43 | }, 44 | /** 45 | * remove the last key from internal array 46 | */ 47 | pop: function () { 48 | this.key.pop() 49 | }, 50 | /** 51 | * join the keys 52 | */ 53 | join: function (key) { 54 | let out = '' 55 | key = key || this.key 56 | if (typeof key === 'string') { 57 | key = [key] 58 | } 59 | 60 | key.forEach(k => { 61 | if (KEY.test(k)) { 62 | out += '.' + k 63 | } else { 64 | out += '[' + Ref.wrapkey(k, this._opts) + ']' 65 | } 66 | }) 67 | return out 68 | }, 69 | /** 70 | * check if object `source` has an already known reference. 71 | * If so then origin and source are stored in `opts.reference` 72 | * @param {Object} source - object to compare 73 | * @return {Boolean} 74 | */ 75 | hasReference: function (source) { 76 | let idx 77 | if (~(idx = this.refs.indexOf(source))) { 78 | this.references.push([this.join(), this.keys[idx]]) 79 | return true 80 | } else { 81 | this.refs.push(source) 82 | this.keys.push(this.join()) 83 | } 84 | }, 85 | /** 86 | * get the references array 87 | * @return {Array} references array 88 | */ 89 | getReferences: function () { 90 | return this.references 91 | } 92 | } 93 | 94 | module.exports = Ref 95 | -------------------------------------------------------------------------------- /src/internal/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const UNSAFE_CHARS_REGEXP = /[<>\u2028\u2029/\\\r\n\t"]/g 4 | const CHARS_REGEXP = /[\\\r\n\t"]/g 5 | 6 | const UNICODE_CHARS = { 7 | '"': '\\"', 8 | '\n': '\\n', 9 | '\r': '\\r', 10 | '\t': '\\t', 11 | '\\': '\\u005C', 12 | '<': '\\u003C', 13 | '>': '\\u003E', 14 | '/': '\\u002F', 15 | '\u2028': '\\u2028', 16 | '\u2029': '\\u2029' 17 | } 18 | 19 | function safeString (str) { 20 | return str.replace(UNSAFE_CHARS_REGEXP, (unsafeChar) => { 21 | return UNICODE_CHARS[unsafeChar] 22 | }) 23 | } 24 | 25 | function unsafeString (str) { 26 | str = str.replace(CHARS_REGEXP, (unsafeChar) => UNICODE_CHARS[unsafeChar]) 27 | return str 28 | } 29 | 30 | function quote (str, opts) { 31 | const fn = opts.unsafe ? unsafeString : safeString 32 | return str ? `"${fn(str)}"` : '' 33 | } 34 | 35 | function saferFunctionString (str, opts) { 36 | return opts.unsafe 37 | ? str 38 | : str.replace(/(<\/?)([a-z][^>]*?>)/ig, (m, m1, m2) => safeString(m1) + m2) 39 | } 40 | 41 | function isObject (arg) { 42 | return typeof arg === 'object' && arg !== null 43 | } 44 | 45 | function isBuffer (arg) { 46 | return arg instanceof Buffer 47 | } 48 | 49 | function isInvalidDate (arg) { 50 | return isNaN(arg.getTime()) 51 | } 52 | 53 | function toType (o) { 54 | const _type = Object.prototype.toString.call(o) 55 | const type = _type.substring(8, _type.length - 1) 56 | if (type === 'Uint8Array' && isBuffer(o)) return 'Buffer' 57 | return type 58 | } 59 | 60 | module.exports = { 61 | safeString, 62 | unsafeString, 63 | quote, 64 | saferFunctionString, 65 | isBuffer, 66 | isObject, 67 | isInvalidDate, 68 | toType 69 | } 70 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | /* eslint no-new-func: off */ 2 | 3 | 'use strict' 4 | 5 | const assert = require('assert') 6 | const serialize = require('../src') 7 | 8 | if (typeof assert.deepStrictEqual === 'undefined') { 9 | assert.deepStrictEqual = assert.deepEqual // eslint-disable-line 10 | } 11 | 12 | const isBrowser = (typeof window !== 'undefined') 13 | 14 | function log (arg) { 15 | // eslint-disable-next-line no-console 16 | console.log(JSON.stringify(arg)) 17 | } 18 | 19 | const isLessV12 = parseInt(process.versions.node.split('.')[0]) < 12 20 | 21 | describe.node = isBrowser ? describe.skip : describe 22 | 23 | describe('serialize-to-js', function () { 24 | function test (name, inp, exp, unsafe) { 25 | it(name, function () { 26 | const res = serialize(inp, { unsafe }) 27 | if (typeof exp === 'object') { 28 | assert.deepStrictEqual(res, exp) 29 | } else { 30 | assert.strictEqual(res, exp) 31 | } 32 | }) 33 | } 34 | 35 | describe('safe mode', function () { 36 | test('undefined', undefined, 'undefined') 37 | test('null', null, 'null') 38 | test('boolean', true, 'true') 39 | test('number', 3.1415, '3.1415') 40 | test('zero', 0, '0') 41 | test('number int', 3, '3') 42 | test('number negative int', -13, '-13') 43 | test('number float', 0.1, '0.1') 44 | test('number negative float', -0.2, '-0.2') 45 | test('NaN', NaN, 'NaN') 46 | test('Infinity', Infinity, 'Infinity') 47 | test('string', "string's\n\"new\" line", '"string\'s\\n\\"new\\" line"') 48 | test('empty string', '', '""') 49 | test('nul string', '\0', '"\u0000"') 50 | test('string with unsafe characters', 51 | '', 52 | '"\\u003Cscript type=\\"application\\u002Fjavascript\\"\\u003E\\u2028\\u2029\\nvar a = 0;\\nvar b = 1; a \\u003E 1;\\n\\u003C\\u002Fscript\\u003E"' 53 | ) 54 | test('string with all unsafe characters', '<>\\\\ \t\n/', '"\\u003C\\u003E\\u005C\\u005C \\t\\n\\u002F"') 55 | test('empty object', {}, '{}') 56 | test('object', { a: 1, b: 2 }, '{a: 1, b: 2}') 57 | test('object with backslash', { backslash: '\\' }, '{backslash: "\\u005C"}') 58 | test('object of primitives', 59 | { one: true, two: false, 'thr-ee': undefined, four: 1, 5: 3.1415, six: -17, 'se ven': 'string' }, 60 | '{"5": 3.1415, one: true, two: false, "thr-ee": undefined, four: 1, six: -17, "se ven": "string"}' 61 | ) 62 | test('object with unsafe property name', 63 | { "', 150 | '""', 151 | true 152 | ) 153 | test('object with unsafe property name', 154 | { "