├── .bumpedrc ├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── lib ├── defaults.js ├── index.d.ts ├── index.js └── util.js ├── package.json └── test ├── compare.js ├── mocha.opts ├── nested.js ├── replacer.js ├── space.js ├── stringify.js └── to_json.js /.bumpedrc: -------------------------------------------------------------------------------- 1 | files: [ 2 | 'package.json' 3 | ] 4 | 5 | plugins: 6 | 7 | prerelease: 8 | 9 | 'Linting config files': 10 | plugin: 'bumped-finepack' 11 | 12 | postrelease: 13 | 14 | 'Generating CHANGELOG file': 15 | plugin: 'bumped-changelog' 16 | 17 | 'Commiting new version': 18 | plugin: 'bumped-terminal' 19 | command: 'git add CHANGELOG.md package.json && git commit -m "Release $newVersion"' 20 | 21 | 'Detecting problems before publish': 22 | plugin: 'bumped-terminal' 23 | command: 'git-dirty && npm test' 24 | 25 | 'Publishing tag at GitHub': 26 | plugin: 'bumped-terminal' 27 | command: 'git tag $newVersion && git push && git push --tags' 28 | 29 | 'Publishing at NPM': 30 | plugin: 'bumped-terminal' 31 | command: 'npm publish' 32 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | max_line_length = 80 13 | indent_brace_style = 1TBS 14 | spaces_around_operators = true 15 | quote_type = auto 16 | 17 | [package.json] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | # Check for updates to GitHub Actions every weekday 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: lts/* 21 | - name: Setup PNPM 22 | uses: pnpm/action-setup@v4 23 | with: 24 | version: latest 25 | run_install: true 26 | - name: Test 27 | run: npm test 28 | - name: Report 29 | run: npx c8 report --reporter=text-lcov > coverage/lcov.info 30 | - name: Coverage 31 | uses: coverallsapp/github-action@main 32 | with: 33 | github-token: ${{ secrets.GITHUB_TOKEN }} 34 | - name: Release 35 | if: ${{ github.ref == 'refs/heads/master' && !startsWith(github.event.head_commit.message, 'chore(release):') && !startsWith(github.event.head_commit.message, 'docs:') }} 36 | env: 37 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 38 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 39 | run: | 40 | git config --global user.email ${{ secrets.GIT_EMAIL }} 41 | git config --global user.name ${{ secrets.GIT_USERNAME }} 42 | npm run release 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################ 2 | # npm 3 | ############################ 4 | node_modules 5 | npm-debug.log 6 | 7 | ############################ 8 | # tmp, editor & OS files 9 | ############################ 10 | .tmp 11 | *.swo 12 | *.swp 13 | *.swn 14 | *.swm 15 | .DS_Store 16 | *# 17 | *~ 18 | .idea 19 | *sublime* 20 | nbproject 21 | 22 | ############################ 23 | # Tests 24 | ############################ 25 | testApp 26 | coverage 27 | .nyc_output 28 | 29 | ############################ 30 | # Other 31 | ############################ 32 | .node_history 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .project 3 | *.sublime-* 4 | .DS_Store 5 | *.seed 6 | *.log 7 | *.csv 8 | *.dat 9 | *.out 10 | *.pid 11 | *.swp 12 | *.swo 13 | node_modules 14 | coverage 15 | *.tgz 16 | *.xml 17 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | unsafe-perm=true 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "lts/*" 5 | - "4" 6 | after_success: npm run coveralls 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### 1.0.12 (2023-10-24) 6 | 7 | ### 1.0.11 (2023-09-07) 8 | 9 | ### 1.0.10 (2023-07-20) 10 | 11 | 12 | ### Bug Fixes 13 | 14 | * define Options interface in TypeScript ([#17](https://github.com/kikobeats/json-stringify-deterministic/issues/17)) ([d30b0f0](https://github.com/kikobeats/json-stringify-deterministic/commit/d30b0f055d90676f72d10fb1d6d3e80d9c3d3724)) 15 | 16 | ### 1.0.9 (2023-07-20) 17 | 18 | ### 1.0.8 (2022-12-14) 19 | 20 | ### 1.0.7 (2022-05-17) 21 | 22 | ### 1.0.6 (2022-04-11) 23 | 24 | ### 1.0.5 (2022-04-01) 25 | 26 | ### 1.0.4 (2022-03-02) 27 | 28 | ### 1.0.3 (2022-02-24) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * add nano config ([4234c7f](https://github.com/kikobeats/json-stringify-deterministic/commit/4234c7f5c4072f17d47ceb15ad53fa7e6c4848a1)) 34 | 35 | ### 1.0.2 (2021-12-22) 36 | 37 | 38 | ## 1.0.1 (2017-07-03) 39 | 40 | * Fix linter ([69774ca](https://github.com/kikobeats/json-stringify-deterministic/commit/69774ca)) 41 | * Refactor ([6948c3a](https://github.com/kikobeats/json-stringify-deterministic/commit/6948c3a)) 42 | * Update README.md ([77cd785](https://github.com/kikobeats/json-stringify-deterministic/commit/77cd785)) 43 | * Update travis builds ([4c91c4c](https://github.com/kikobeats/json-stringify-deterministic/commit/4c91c4c)) 44 | 45 | 46 | 47 | 48 | # 1.0.0 (2016-09-05) 49 | 50 | * Add disclaimer ([e798bb5](https://github.com/kikobeats/json-stringify-deterministic/commit/e798bb5)) 51 | * Add docs ([1f96b55](https://github.com/kikobeats/json-stringify-deterministic/commit/1f96b55)) 52 | * Better cycles detection ([6029a10](https://github.com/kikobeats/json-stringify-deterministic/commit/6029a10)) 53 | * Drop old node support ([93acbc2](https://github.com/kikobeats/json-stringify-deterministic/commit/93acbc2)) 54 | * Extract defaults ([cde3292](https://github.com/kikobeats/json-stringify-deterministic/commit/cde3292)) 55 | * Extract util ([70a230c](https://github.com/kikobeats/json-stringify-deterministic/commit/70a230c)) 56 | * first commit ([e976283](https://github.com/kikobeats/json-stringify-deterministic/commit/e976283)) 57 | * Fix style ([00ef3d2](https://github.com/kikobeats/json-stringify-deterministic/commit/00ef3d2)) 58 | * Less cool ([5747e9f](https://github.com/kikobeats/json-stringify-deterministic/commit/5747e9f)) 59 | * Refactor ([fa72817](https://github.com/kikobeats/json-stringify-deterministic/commit/fa72817)) 60 | * Renaming tests ([0beaf98](https://github.com/kikobeats/json-stringify-deterministic/commit/0beaf98)) 61 | * Support serialize regex ([0427d66](https://github.com/kikobeats/json-stringify-deterministic/commit/0427d66)) 62 | * Update ([cb03ce6](https://github.com/kikobeats/json-stringify-deterministic/commit/cb03ce6)) 63 | * WIP ([2be2848](https://github.com/kikobeats/json-stringify-deterministic/commit/2be2848)) 64 | * WIP ([8c02d17](https://github.com/kikobeats/json-stringify-deterministic/commit/8c02d17)) 65 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2016 Kiko Beats (https://github.com/Kikobeats) 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 | # json-stringify-deterministic 2 | 3 | ![Last version](https://img.shields.io/github/tag/Kikobeats/json-stringify-deterministic.svg?style=flat-square) 4 | [![Coverage Status](https://img.shields.io/coveralls/Kikobeats/json-stringify-deterministic.svg?style=flat-square)](https://coveralls.io/github/Kikobeats/json-stringify-deterministic) 5 | [![NPM Status](https://img.shields.io/npm/dm/json-stringify-deterministic.svg?style=flat-square)](https://www.npmjs.org/package/json-stringify-deterministic) 6 | 7 | > Deterministic version of `JSON.stringify()`, so you can get a consistent hash from stringified results. 8 | 9 | Similar to [json-stable-stringify](https://github.com/substack/json-stable-stringify) *but*: 10 | 11 | - No Dependencies. Minimal as possible. 12 | - Better cycles detection. 13 | - Support serialization for object without `.toJSON` (such as `RegExp`). 14 | - Provides built-in TypeScript declarations. 15 | 16 | ## Install 17 | 18 | ```bash 19 | npm install json-stringify-deterministic --save 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```js 25 | const stringify = require('json-stringify-deterministic') 26 | const obj = { c: 8, b: [{ z: 6, y: 5, x: 4 }, 7], a: 3 } 27 | 28 | console.log(stringify(obj)) 29 | // => {"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8} 30 | ``` 31 | 32 | ## API 33 | 34 | ### stringify(<obj>, [opts]) 35 | 36 | #### obj 37 | 38 | *Required*
39 | Type: `object` 40 | 41 | The input `object` to be serialized. 42 | 43 | #### opts 44 | 45 | ##### opts.stringify 46 | 47 | Type: `function` 48 | Default: `JSON.stringify` 49 | 50 | Determinate how to stringify primitives values. 51 | 52 | ##### opts.cycles 53 | 54 | Type: `boolean` 55 | Default: `false` 56 | 57 | Determinate how to resolve cycles. 58 | 59 | Under `true`, when a cycle is detected, `[Circular]` will be inserted in the node. 60 | 61 | ##### opts.compare 62 | 63 | Type: `function` 64 | 65 | Custom comparison function for object keys. 66 | 67 | Your function `opts.compare` is called with these parameters: 68 | 69 | ``` js 70 | opts.cmp({ key: akey, value: avalue }, { key: bkey, value: bvalue }) 71 | ``` 72 | 73 | For example, to sort on the object key names in reverse order you could write: 74 | 75 | ``` js 76 | const stringify = require('json-stringify-deterministic') 77 | 78 | const obj = { c: 8, b: [{z: 6,y: 5,x: 4}, 7], a: 3 } 79 | const objSerializer = stringify(obj, function (a, b) { 80 | return a.key < b.key ? 1 : -1 81 | }) 82 | 83 | console.log(objSerializer) 84 | // => {"c":8,"b":[{"z":6,"y":5,"x":4},7],"a":3} 85 | ``` 86 | 87 | Or if you wanted to sort on the object values in reverse order, you could write: 88 | 89 | ```js 90 | const stringify = require('json-stringify-deterministic') 91 | 92 | const obj = { d: 6, c: 5, b: [{ z: 3, y: 2, x: 1 }, 9], a: 10 } 93 | const objtSerializer = stringify(obj, function (a, b) { 94 | return a.value < b.value ? 1 : -1 95 | }) 96 | 97 | console.log(objtSerializer) 98 | // => {"d":6,"c":5,"b":[{"z":3,"y":2,"x":1},9],"a":10} 99 | ``` 100 | 101 | ##### opts.space 102 | 103 | Type: `string`
104 | Default: `''` 105 | 106 | If you specify `opts.space`, it will indent the output for pretty-printing. 107 | 108 | Valid values are strings (e.g. `{space: \t}`). For example: 109 | 110 | ```js 111 | const stringify = require('json-stringify-deterministic') 112 | 113 | const obj = { b: 1, a: { foo: 'bar', and: [1, 2, 3] } } 114 | const objSerializer = stringify(obj, { space: ' ' }) 115 | console.log(objSerializer) 116 | // => { 117 | // "a": { 118 | // "and": [ 119 | // 1, 120 | // 2, 121 | // 3 122 | // ], 123 | // "foo": "bar" 124 | // }, 125 | // "b": 1 126 | // } 127 | ``` 128 | 129 | ##### opts.replacer 130 | 131 | Type: `function`
132 | 133 | The replacer parameter is a function `opts.replacer(key, value)` that behaves 134 | the same as the replacer 135 | [from the core JSON object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_native_JSON#The_replacer_parameter). 136 | 137 | ## Related 138 | 139 | - [sort-keys-recursive](https://github.com/Kikobeats/sort-keys-recursive): Sort the keys of an array/object recursively. 140 | 141 | ## License 142 | 143 | MIT © [Kiko Beats](https://github.com/Kikobeats). 144 | -------------------------------------------------------------------------------- /lib/defaults.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | space: '', 3 | cycles: false, 4 | replacer: (k, v) => v, 5 | stringify: JSON.stringify 6 | } 7 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | interface Options { 2 | /** 3 | * Determinate how to stringify primitives values. 4 | * @default JSON.stringify 5 | */ 6 | stringify?: typeof JSON["stringify"]; 7 | 8 | /** 9 | * Determinate how to resolve cycles. 10 | * Under true, when a cycle is detected, [Circular] will be inserted in the node. 11 | * @default false 12 | */ 13 | cycles?: boolean; 14 | 15 | /** 16 | * Custom comparison function for object keys. 17 | * @param a first key-value pair. 18 | * @param b second key-value pair. 19 | * @returns a number whose sign indicates the relative order of the two elements. 20 | */ 21 | compare?: (a: KeyValue, b: KeyValue) => number; 22 | 23 | /** 24 | * Indent the output for pretty-printing. 25 | */ 26 | space?: string; 27 | 28 | /** 29 | * Replacer function that behaves the same as the replacer from the core JSON object. 30 | */ 31 | replacer?: (key: string, value: unknown) => unknown; 32 | } 33 | 34 | interface KeyValue { 35 | key: string; 36 | value: unknown; 37 | } 38 | 39 | /** 40 | * Deterministic version of JSON.stringify(), so you can get a consistent hash from stringified results. 41 | * @param obj The input object to be serialized. 42 | * @param opts options. 43 | */ 44 | declare function stringify(obj: unknown, opts?: Options): string; 45 | 46 | export = stringify; 47 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const DEFAULTS = require('./defaults') 4 | const isFunction = require('./util').isFunction 5 | const isBoolean = require('./util').isBoolean 6 | const isObject = require('./util').isObject 7 | const isArray = require('./util').isArray 8 | const isRegex = require('./util').isRegex 9 | const assign = require('./util').assign 10 | const keys = require('./util').keys 11 | 12 | function serialize (obj) { 13 | if (obj === null || obj === undefined) return obj 14 | if (isRegex(obj)) return obj.toString() 15 | return obj.toJSON ? obj.toJSON() : obj 16 | } 17 | 18 | function stringifyDeterministic (obj, opts) { 19 | opts = opts || assign({}, DEFAULTS) 20 | 21 | if (isFunction(opts)) opts = { compare: opts } 22 | 23 | const space = opts.space || DEFAULTS.space 24 | const cycles = isBoolean(opts.cycles) ? opts.cycles : DEFAULTS.cycles 25 | const replacer = opts.replacer || DEFAULTS.replacer 26 | const stringify = opts.stringify || DEFAULTS.stringify 27 | 28 | const compare = opts.compare && (function (f) { 29 | return function (node) { 30 | return function (a, b) { 31 | const aobj = { key: a, value: node[a] } 32 | const bobj = { key: b, value: node[b] } 33 | return f(aobj, bobj) 34 | } 35 | } 36 | })(opts.compare) 37 | 38 | // Detect circular structure in obj and raise error efficiently. 39 | if (!cycles) stringify(obj) 40 | 41 | const seen = [] 42 | 43 | return (function _deterministic (parent, key, node, level) { 44 | const indent = space ? ('\n' + new Array(level + 1).join(space)) : '' 45 | const colonSeparator = space ? ': ' : ':' 46 | 47 | node = serialize(node) 48 | node = replacer.call(parent, key, node) 49 | 50 | if (node === undefined) return 51 | 52 | if (!isObject(node) || node === null) return stringify(node) 53 | 54 | if (isArray(node)) { 55 | const out = [] 56 | for (let i = 0; i < node.length; i++) { 57 | const item = _deterministic(node, i, node[i], level + 1) || stringify(null) 58 | out.push(indent + space + item) 59 | } 60 | return '[' + out.join(',') + indent + ']' 61 | } else { 62 | if (cycles) { 63 | if (seen.indexOf(node) !== -1) { 64 | return stringify('[Circular]') 65 | } else { 66 | seen.push(node) 67 | } 68 | } 69 | 70 | const nodeKeys = keys(node).sort(compare && compare(node)) 71 | const out = [] 72 | for (let i = 0; i < nodeKeys.length; i++) { 73 | const key = nodeKeys[i] 74 | const value = _deterministic(node, key, node[key], level + 1) 75 | 76 | if (!value) continue 77 | 78 | const keyValue = stringify(key) + colonSeparator + value 79 | out.push(indent + space + keyValue) 80 | } 81 | seen.splice(seen.indexOf(node), 1) 82 | return '{' + out.join(',') + indent + '}' 83 | } 84 | })({ '': obj }, '', obj, 0) 85 | } 86 | 87 | module.exports = stringifyDeterministic 88 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | isArray: Array.isArray, 5 | assign: Object.assign, 6 | isObject: v => typeof v === 'object', 7 | isFunction: v => typeof v === 'function', 8 | isBoolean: v => typeof v === 'boolean', 9 | isRegex: v => v instanceof RegExp, 10 | keys: Object.keys 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-stringify-deterministic", 3 | "description": "deterministic version of JSON.stringify() so you can get a consistent hash from stringified results.", 4 | "homepage": "https://github.com/Kikobeats/json-stringify-deterministic", 5 | "version": "1.0.12", 6 | "types": "./lib/index.d.ts", 7 | "main": "lib", 8 | "author": { 9 | "email": "josefrancisco.verdu@gmail.com", 10 | "name": "Kiko Beats", 11 | "url": "https://github.com/Kikobeats" 12 | }, 13 | "contributors": [ 14 | { 15 | "name": "Junxiao Shi", 16 | "email": "sunnylandh@gmail.com" 17 | } 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/kikobeats/json-stringify-deterministic.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/Kikobeats/json-stringify-deterministic/issues" 25 | }, 26 | "keywords": [ 27 | "deterministic", 28 | "hash", 29 | "json", 30 | "sort", 31 | "stable", 32 | "stringify" 33 | ], 34 | "devDependencies": { 35 | "@commitlint/cli": "latest", 36 | "@commitlint/config-conventional": "latest", 37 | "@ksmithut/prettier-standard": "latest", 38 | "c8": "latest", 39 | "ci-publish": "latest", 40 | "finepack": "latest", 41 | "git-authors-cli": "latest", 42 | "github-generate-release": "latest", 43 | "mocha": "latest", 44 | "nano-staged": "latest", 45 | "should": "latest", 46 | "simple-git-hooks": "latest", 47 | "standard": "latest", 48 | "standard-markdown": "latest", 49 | "standard-version": "latest" 50 | }, 51 | "engines": { 52 | "node": ">= 4" 53 | }, 54 | "files": [ 55 | "index.js", 56 | "lib" 57 | ], 58 | "scripts": { 59 | "clean": "rm -rf node_modules", 60 | "contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true", 61 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 62 | "lint": "standard && standard-markdown", 63 | "postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)", 64 | "prerelease": "npm run update:check && npm run contributors", 65 | "pretest": "npm run lint", 66 | "release": "standard-version -a", 67 | "release:github": "github-generate-release", 68 | "release:tags": "git push --follow-tags origin HEAD:master", 69 | "test": "c8 mocha --require should" 70 | }, 71 | "license": "MIT", 72 | "commitlint": { 73 | "extends": [ 74 | "@commitlint/config-conventional" 75 | ], 76 | "rules": { 77 | "body-max-line-length": [ 78 | 0 79 | ] 80 | } 81 | }, 82 | "nano-staged": { 83 | "*.js": [ 84 | "prettier-standard", 85 | "standard --fix" 86 | ], 87 | "*.md": [ 88 | "standard-markdown" 89 | ], 90 | "package.json": [ 91 | "finepack" 92 | ] 93 | }, 94 | "simple-git-hooks": { 95 | "commit-msg": "npx commitlint --edit", 96 | "pre-commit": "npx nano-staged" 97 | }, 98 | "standard": { 99 | "globals": [ 100 | "describe", 101 | "it" 102 | ] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/compare.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const stringify = require('..') 4 | 5 | describe('compare', function () { 6 | it('custom comparison function', function () { 7 | const obj = { c: 8, b: [{ z: 6, y: 5, x: 4 }, 7], a: 3 } 8 | const s = stringify(obj, function (a, b) { 9 | return a.key < b.key ? 1 : -1 10 | }) 11 | 12 | s.should.be.equal('{"c":8,"b":[{"z":6,"y":5,"x":4},7],"a":3}') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require should 2 | --reporter spec 3 | --timeout 120000 4 | --slow 300 5 | --bail 6 | --recursive 7 | -------------------------------------------------------------------------------- /test/nested.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const stringify = require('..') 4 | 5 | describe('nested', function () { 6 | it('nested', function () { 7 | const obj = { c: 8, b: [{ z: 6, y: 5, x: 4 }, 7], a: 3 } 8 | stringify(obj).should.be.equal('{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}') 9 | }) 10 | 11 | it('cyclic (default)', function () { 12 | const one = { a: 1 } 13 | const two = { a: 2, one: one } 14 | one.two = two 15 | try { 16 | stringify(one) 17 | } catch (ex) { 18 | ex.toString() 19 | .startsWith('TypeError: Converting circular structure to JSON') 20 | .should.be.true() 21 | } 22 | }) 23 | 24 | it('cyclic (specifically allowed)', function () { 25 | const one = { a: 1 } 26 | const two = { a: 2, one: one } 27 | one.two = two 28 | stringify(one, { cycles: true }).should.be.equal( 29 | '{"a":1,"two":{"a":2,"one":"[Circular]"}}' 30 | ) 31 | }) 32 | 33 | it('repeated non-cyclic value', function () { 34 | const one = { x: 1 } 35 | const two = { a: one, b: one } 36 | stringify(two).should.be.equal('{"a":{"x":1},"b":{"x":1}}') 37 | }) 38 | 39 | it('acyclic but with reused obj-property pointers', function () { 40 | const x = { a: 1 } 41 | const y = { b: x, c: x } 42 | stringify(y).should.be.equal('{"b":{"a":1},"c":{"a":1}}') 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/replacer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const stringify = require('..') 4 | 5 | describe('replacer', function () { 6 | it('replace root', function () { 7 | const obj = { a: 1, b: 2, c: false } 8 | const replacer = function (key, value) { return 'one' } 9 | 10 | stringify(obj, { replacer: replacer }).should.be.equal('"one"') 11 | }) 12 | 13 | it('replace numbers', function () { 14 | const obj = { a: 1, b: 2, c: false } 15 | const replacer = function (key, value) { 16 | if (value === 1) return 'one' 17 | if (value === 2) return 'two' 18 | return value 19 | } 20 | 21 | stringify(obj, { replacer: replacer }).should.be.equal('{"a":"one","b":"two","c":false}') 22 | }) 23 | 24 | it('replace with object', function () { 25 | const obj = { a: 1, b: 2, c: false } 26 | const replacer = function (key, value) { 27 | if (key === 'b') return { d: 1 } 28 | if (value === 1) return 'one' 29 | return value 30 | } 31 | 32 | stringify(obj, { replacer: replacer }).should.be.equal('{"a":"one","b":{"d":"one"},"c":false}') 33 | }) 34 | 35 | it('replace with undefined', function () { 36 | const obj = { a: 1, b: 2, c: false } 37 | const replacer = function (key, value) { 38 | if (value === false) return 39 | return value 40 | } 41 | 42 | stringify(obj, { replacer: replacer }).should.be.equal('{"a":1,"b":2}') 43 | }) 44 | 45 | it('replace with array', function () { 46 | const obj = { a: 1, b: 2, c: false } 47 | const replacer = function (key, value) { 48 | if (key === 'b') return ['one', 'two'] 49 | return value 50 | } 51 | 52 | stringify(obj, { replacer: replacer }).should.be.equal('{"a":1,"b":["one","two"],"c":false}') 53 | }) 54 | 55 | it('replace array item', function () { 56 | const obj = { a: 1, b: 2, c: [1, 2] } 57 | const replacer = function (key, value) { 58 | if (value === 1) return 'one' 59 | if (value === 2) return 'two' 60 | return value 61 | } 62 | 63 | stringify(obj, { replacer: replacer }).should.be.equal('{"a":"one","b":"two","c":["one","two"]}') 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /test/space.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const stringify = require('..') 4 | 5 | describe('space', function () { 6 | it('space parameter', function () { 7 | const obj = { one: 1, two: 2 } 8 | stringify(obj, { space: ' ' }).should.be.equal('' + 9 | '{\n' + 10 | ' "one": 1,\n' + 11 | ' "two": 2\n' + 12 | '}' 13 | ) 14 | }) 15 | 16 | it('space parameter (with tabs)', function () { 17 | const obj = { one: 1, two: 2 } 18 | stringify(obj, { space: '\t' }).should.be.equal('' + 19 | '{\n' + 20 | '\t"one": 1,\n' + 21 | '\t"two": 2\n' + 22 | '}' 23 | ) 24 | }) 25 | 26 | it('space parameter (nested objects)', function () { 27 | const obj = { one: 1, two: { b: 4, a: [2, 3] } } 28 | stringify(obj, { space: ' ' }).should.be.equal('' + 29 | '{\n' + 30 | ' "one": 1,\n' + 31 | ' "two": {\n' + 32 | ' "a": [\n' + 33 | ' 2,\n' + 34 | ' 3\n' + 35 | ' ],\n' + 36 | ' "b": 4\n' + 37 | ' }\n' + 38 | '}' 39 | ) 40 | }) 41 | 42 | it('space parameter (same as native)', function () { 43 | // for this test, properties need to be in alphabetical order 44 | const obj = { one: 1, two: { a: [2, 3], b: 4 } } 45 | stringify(obj, { space: ' ' }).should.be.equal(JSON.stringify(obj, null, ' ')) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /test/stringify.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const stringify = require('..') 4 | 5 | describe('stringify', function () { 6 | it('simple object', function () { 7 | const obj = { c: 6, b: [4, 5], a: 3, z: null } 8 | stringify(obj).should.be.equal('{"a":3,"b":[4,5],"c":6,"z":null}') 9 | }) 10 | 11 | describe('undefined', function () { 12 | it('in object', function () { 13 | const obj = { a: 3, z: undefined } 14 | stringify(obj).should.be.equal('{"a":3}') 15 | }) 16 | 17 | it('in array', function () { 18 | const obj = [4, undefined, 6] 19 | stringify(obj).should.be.equal('[4,null,6]') 20 | }) 21 | }) 22 | 23 | describe('empty string', function () { 24 | it('in object', function () { 25 | const obj = { a: 3, z: '' } 26 | stringify(obj).should.be.equal('{"a":3,"z":""}') 27 | }) 28 | 29 | it('in array', function () { 30 | const obj = [4, '', 6] 31 | stringify(obj).should.be.equal('[4,"",6]') 32 | }) 33 | }) 34 | 35 | describe('regex', function () { 36 | it('in object', function () { 37 | const obj = { a: 3, z: /foobar/ } 38 | stringify(obj).should.be.equal('{"a":3,"z":"/foobar/"}') 39 | }) 40 | 41 | it('in array', function () { 42 | const obj = [4, undefined, /foobar/] 43 | stringify(obj).should.be.equal('[4,null,"/foobar/"]') 44 | }) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /test/to_json.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const stringify = require('..') 4 | 5 | describe('toJSON', function () { 6 | it('function', function () { 7 | const obj = { one: 1, two: 2, toJSON: function () { return { one: 1 } } } 8 | stringify(obj).should.be.equal('{"one":1}') 9 | }) 10 | 11 | it('string', function () { 12 | const obj = { one: 1, two: 2, toJSON: function () { return 'one' } } 13 | stringify(obj).should.be.equal('"one"') 14 | }) 15 | 16 | it('array', function () { 17 | const obj = { one: 1, two: 2, toJSON: function () { return ['one'] } } 18 | stringify(obj).should.be.equal('["one"]') 19 | }) 20 | }) 21 | --------------------------------------------------------------------------------