diff --git a/src/components/DataKeyPair.tsx b/src/components/DataKeyPair.tsx index 685cc237..4d7d7415 100644 --- a/src/components/DataKeyPair.tsx +++ b/src/components/DataKeyPair.tsx @@ -65,6 +65,7 @@ export const DataKeyPair: React.FC = (props) => { store => store.colorspace.base0C) const { Component, PreComponent, PostComponent, Editor } = useTypeComponents( value) + const quotesOnKeys = useJsonViewerStore(store => store.quotesOnKeys) const rootName = useJsonViewerStore(store => store.rootName) const isRoot = root === value const isNumberKey = Number.isInteger(Number(key)) @@ -164,7 +165,18 @@ export const DataKeyPair: React.FC = (props) => { ) }, - [Editor, copied, copy, editable, editing, enableClipboard, onChange, path, tempValue, value]) + [ + Editor, + copied, + copy, + editable, + editing, + enableClipboard, + onChange, + path, + tempValue, + value + ]) const expandable = !!(PreComponent && PostComponent) const KeyRenderer = useJsonViewerStore(store => store.keyRenderer) @@ -220,7 +232,7 @@ export const DataKeyPair: React.FC = (props) => { isNumberKey ? {key} - : <>"{key}" + : quotesOnKeys ? <>"{key}" : <>{key} ) } { diff --git a/src/components/DataTypes/Object.tsx b/src/components/DataTypes/Object.tsx index a66dc915..b9910c39 100644 --- a/src/components/DataTypes/Object.tsx +++ b/src/components/DataTypes/Object.tsx @@ -112,6 +112,7 @@ export const ObjectType: React.FC> = (props) => { const [displayLength, setDisplayLength] = useState( useJsonViewerStore(store => store.maxDisplayLength) ) + const objectSortKeys = useJsonViewerStore(store => store.objectSortKeys) const elements = useMemo(() => { if (!props.inspect) { return null @@ -188,7 +189,16 @@ export const ObjectType: React.FC> = (props) => { }) } else { // object - const entries = Object.entries(value) + let entries: [key: string, value: unknown][] = Object.entries(value) + if (objectSortKeys) { + entries = entries.sort(([a], [b]) => { + if (objectSortKeys === true) { + return a.localeCompare(b) + } else { + return objectSortKeys(a, b) + } + }) + } const elements = entries.slice(0, displayLength).map(([key, value]) => { const path = [...props.path, key] return ( @@ -221,7 +231,9 @@ export const ObjectType: React.FC> = (props) => { props.path, groupArraysAfterLength, displayLength, - keyColor]) + keyColor, + objectSortKeys + ]) const marginLeft = props.inspect ? 0.6 : 0 const width = useJsonViewerStore(store => store.indentWidth) const indentWidth = props.inspect ? width - marginLeft : width diff --git a/src/stores/JsonViewerStore.ts b/src/stores/JsonViewerStore.ts index 0e6ba4aa..edb03d8f 100644 --- a/src/stores/JsonViewerStore.ts +++ b/src/stores/JsonViewerStore.ts @@ -20,6 +20,8 @@ export type JsonViewerState = { maxDisplayLength: number defaultInspectDepth: number collapseStringsAfterLength: number + objectSortKeys: boolean | ((a: string, b: string) => number) + quotesOnKeys: boolean colorspace: Colorspace editable: boolean | ((path: Path, currentValue: U) => boolean) rootName: false | string @@ -50,6 +52,8 @@ export const createJsonViewerStore = (props: JsonViewerProps) => keyRenderer: props.keyRenderer ?? DefaultKeyRenderer, editable: props.editable ?? true, defaultInspectDepth: props.defaultInspectDepth ?? 5, + objectSortKeys: props.objectSortKeys ?? false, + quotesOnKeys: props.quotesOnKeys ?? true, // internal state inspectCache: {}, hoverPath: null, diff --git a/src/type.ts b/src/type.ts index 80cd836e..e5131ef2 100644 --- a/src/type.ts +++ b/src/type.ts @@ -107,6 +107,23 @@ export type JsonViewerProps = { * @default 100 */ collapseStringsAfterLength?: number + + /** + * Whether sort keys through `String.prototype.localeCompare()` + * + * @default false + */ + objectSortKeys?: boolean | ((a: string, b: string) => number) + + /** + * set `false` to remove quotes from keys + * + * true for `"name"`, false for `name` + * + * @default true + */ + quotesOnKeys?: boolean + className?: string style?: React.CSSProperties /** diff --git a/tests/index.test.tsx b/tests/index.test.tsx index b74f957f..1c047002 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -4,6 +4,61 @@ import { describe, it } from 'vitest' import { JsonViewer } from '../src' +function aPlusB (a: number, b: number) { + return a + b +} + +const loopObject = { + foo: 1, + goo: 'string' +} as Record + +loopObject.self = loopObject + +const loopArray = [ + loopObject +] + +loopArray[1] = loopArray + +const longArray = Array.from({ length: 1000 }).map((_, i) => i) +const map = new Map() +map.set('foo', 1) +map.set('goo', 'hello') +map.set({}, 'world') + +const set = new Set([1, 2, 3]) + +const superLongString = '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111' + +const full = { + loopObject, + loopArray, + longArray, + string: 'this is a string', + integer: 42, + array: [19, 19, 810, 'test', NaN], + nestedArray: [ + [1, 2], + [3, 4] + ], + map, + set, + float: 114.514, + undefined, + superLongString, + object: { + 'first-child': true, + 'second-child': false, + 'last-child': null + }, + fn: aPlusB, + string_number: '1234', + timer: 0, + date: new Date('Tue Sep 13 2022 14:07:44 GMT-0500 (Central Daylight Time)'), + bigint: 110101195306153019n +} + describe('render ', () => { it('render undefined', () => { render() @@ -71,4 +126,24 @@ describe('render ', () => { }}/>) render( a + b}/>) }) + + it('render full', () => { + render() + }) +}) + +describe('render with props', () => { + it('render with quotesOnKeys', () => { + const selection = [true, false] + selection.forEach(quotesOnKeys => { + render() + }) + }) + + it('render with objectSortKeys', () => { + const selection = [true, false, (a: string, b: string) => a.localeCompare(b)] + selection.forEach(objectSortKeys => { + render() + }) + }) })