Skip to content

Commit

Permalink
feat(#57): add json-stable-stringify
Browse files Browse the repository at this point in the history
  • Loading branch information
SukkaW committed Feb 12, 2024
1 parent 25e8525 commit e8f4245
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 1 deletion.
1 change: 1 addition & 0 deletions DOWNLOAD_STATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
| `@nolyfill/is-weakref` | [![npm](https://img.shields.io/npm/dt/@nolyfill/is-weakref.svg?style=flat-square&logo=npm&logoColor=white&label=total%20downloads&color=333)](https://www.npmjs.com/package/@nolyfill/is-weakref) |
| `@nolyfill/isarray` | [![npm](https://img.shields.io/npm/dt/@nolyfill/isarray.svg?style=flat-square&logo=npm&logoColor=white&label=total%20downloads&color=333)](https://www.npmjs.com/package/@nolyfill/isarray) |
| `@nolyfill/iterator.prototype` | [![npm](https://img.shields.io/npm/dt/@nolyfill/iterator.prototype.svg?style=flat-square&logo=npm&logoColor=white&label=total%20downloads&color=333)](https://www.npmjs.com/package/@nolyfill/iterator.prototype) |
| `@nolyfill/json-stable-stringify` | [![npm](https://img.shields.io/npm/dt/@nolyfill/json-stable-stringify.svg?style=flat-square&logo=npm&logoColor=white&label=total%20downloads&color=333)](https://www.npmjs.com/package/@nolyfill/json-stable-stringify) |
| `@nolyfill/jsonify` | [![npm](https://img.shields.io/npm/dt/@nolyfill/jsonify.svg?style=flat-square&logo=npm&logoColor=white&label=total%20downloads&color=333)](https://www.npmjs.com/package/@nolyfill/jsonify) |
| `@nolyfill/object-is` | [![npm](https://img.shields.io/npm/dt/@nolyfill/object-is.svg?style=flat-square&logo=npm&logoColor=white&label=total%20downloads&color=333)](https://www.npmjs.com/package/@nolyfill/object-is) |
| `@nolyfill/object-keys` | [![npm](https://img.shields.io/npm/dt/@nolyfill/object-keys.svg?style=flat-square&logo=npm&logoColor=white&label=total%20downloads&color=333)](https://www.npmjs.com/package/@nolyfill/object-keys) |
Expand Down
3 changes: 2 additions & 1 deletion create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ const singleFilePackagesList = [
['hasown'],
['jsonify'],
['isarray'],
['is-typed-array', { '@nolyfill/which-typed-array': 'workspace:*' }]
['is-typed-array', { '@nolyfill/which-typed-array': 'workspace:*' }],
['json-stable-stringify']
] as const;

const manualPackagesList = [
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"is-weakref": "workspace:@nolyfill/is-weakref@*",
"isarray": "workspace:@nolyfill/isarray@*",
"iterator.prototype": "workspace:@nolyfill/iterator.prototype@*",
"json-stable-stringify": "workspace:@nolyfill/json-stable-stringify@*",
"jsonify": "workspace:@nolyfill/jsonify@*",
"object-is": "workspace:@nolyfill/object-is@*",
"object-keys": "workspace:@nolyfill/object-keys@*",
Expand Down Expand Up @@ -197,6 +198,7 @@
"is-weakref": "npm:@nolyfill/is-weakref@latest",
"isarray": "npm:@nolyfill/isarray@latest",
"iterator.prototype": "npm:@nolyfill/iterator.prototype@latest",
"json-stable-stringify": "npm:@nolyfill/json-stable-stringify@latest",
"jsonify": "npm:@nolyfill/jsonify@latest",
"object-is": "npm:@nolyfill/object-is@latest",
"object-keys": "npm:@nolyfill/object-keys@latest",
Expand Down
125 changes: 125 additions & 0 deletions packages/data/single-file/src/json-stable-stringify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
'use strict';

interface Element {
key: string,
value: any
}

export type Replacer = (key: string, value: any) => any;

interface ComparatorOption {
get?: (key: any) => any
}
export type Comparator = (a: Element, b: Element, opt?: ComparatorOption) => number;

const defaultReplacer: Replacer = function (_key: string, value: any) { return value; };

export interface Options {
/**
* Custom comparator for key
*/
cmp?: Comparator,

/**
* Indent the output for pretty-printing.
*
* Supported is either a string or a number of spaces.
*/
space?: string | number,

/**
* Option to replace values to simpler values
*/
replacer?: Replacer,

/**
* true to allow cycles, by marking the entries as __cycle__.
*/
cycles?: boolean
}

export default function stableStringify(obj: any, opts?: Comparator | Options): string {
const space = opts && 'space' in opts && opts.space
? (typeof opts.space === 'number'
? ' '.repeat(opts.space)
: opts.space)
: '';

const cycles = opts && 'cycles' in opts && typeof opts.cycles === 'boolean'
? opts.cycles
: false;

const replacer = opts && 'replacer' in opts && opts.replacer
? opts.replacer
: defaultReplacer;

const cmpOpt = typeof opts === 'function' ? opts : opts?.cmp;

const cmp = cmpOpt
? ((node: any) => {
const get = cmpOpt.length > 2 && function get(k: any) { return node[k]; };
const thirdArg: ComparatorOption | undefined = get ? { get } : undefined;

return (a: string, b: string) => {
const aobj: Element = { key: a, value: node[a] };
const bobj: Element = { key: b, value: node[b] };
return cmpOpt(aobj, bobj, thirdArg);
};
})
: undefined;

// Cycle
const seen = new Set<any>();

function stringify(parent: any, key: string, node: any, level: number): string {
const indent = space ? `\n${space.repeat(level)}` : '';
const colonSeparator = space ? ': ' : ':';

if (node?.toJSON && typeof node.toJSON === 'function') {
node = node.toJSON();
}

node = replacer.call(parent, key, node);

if (node === undefined) {
// @ts-expect-error -- fuck undefined
return;
}
if (typeof node !== 'object' || node === null) {
return JSON.stringify(node);
}
if (Array.isArray(node)) {
const out = [];
for (let i = 0; i < node.length; i++) {
// @ts-expect-error -- fuck js
const item = stringify(node, i, node[i], level + 1) || JSON.stringify(null);
out.push(indent + space + item);
}
return `[${out.join(',')}${indent}]`;
}

if (seen.has(node)) {
if (cycles) { return JSON.stringify('__cycle__'); }
throw new TypeError('Converting circular structure to JSON');
} else { seen.add(node); }

const keys = Object.keys(node).sort(cmp?.(node));
const out = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = stringify(node, key, node[key], level + 1);

if (!value) { continue; }

const keyValue = JSON.stringify(key)
+ colonSeparator
+ value;

out.push(indent + space + keyValue);
}
seen.delete(node);
return `{${out.join(',')}${indent}}`;
}

return stringify({ '': obj }, '', obj, 0);
}
3 changes: 3 additions & 0 deletions packages/generated/json-stable-stringify/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions packages/generated/json-stable-stringify/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@nolyfill/json-stable-stringify",
"version": "1.0.30",
"repository": {
"type": "git",
"url": "https://github.com/SukkaW/nolyfill",
"directory": "packages/generated/json-stable-stringify"
},
"main": "./index.js",
"license": "MIT",
"files": [
"*.js"
],
"scripts": {},
"dependencies": {},
"engines": {
"node": ">=12.4.0"
}
}
1 change: 1 addition & 0 deletions packages/tools/cli/src/all-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const allPackages = [
"is-weakref",
"isarray",
"iterator.prototype",
"json-stable-stringify",
"jsonify",
"object-is",
"object-keys",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e8f4245

Please sign in to comment.