├── .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 | 
4 | [](https://coveralls.io/github/Kikobeats/json-stringify-deterministic)
5 | [](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 |
--------------------------------------------------------------------------------