Skip to content

Commit b3d392a

Browse files
committed
feat: backport support for props.quotesOnKeys and props.sortKeys
1 parent fbdc09b commit b3d392a

File tree

5 files changed

+124
-4
lines changed

5 files changed

+124
-4
lines changed

src/components/DataKeyPair.tsx

+14-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
6565
store => store.colorspace.base0C)
6666
const { Component, PreComponent, PostComponent, Editor } = useTypeComponents(
6767
value)
68+
const quotesOnKeys = useJsonViewerStore(store => store.quotesOnKeys)
6869
const rootName = useJsonViewerStore(store => store.rootName)
6970
const isRoot = root === value
7071
const isNumberKey = Number.isInteger(Number(key))
@@ -164,7 +165,18 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
164165
</>
165166
)
166167
},
167-
[Editor, copied, copy, editable, editing, enableClipboard, onChange, path, tempValue, value])
168+
[
169+
Editor,
170+
copied,
171+
copy,
172+
editable,
173+
editing,
174+
enableClipboard,
175+
onChange,
176+
path,
177+
tempValue,
178+
value
179+
])
168180

169181
const expandable = !!(PreComponent && PostComponent)
170182
const KeyRenderer = useJsonViewerStore(store => store.keyRenderer)
@@ -220,7 +232,7 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
220232
isNumberKey
221233
? <Box component='span'
222234
style={{ color: numberKeyColor }}>{key}</Box>
223-
: <>&quot;{key}&quot;</>
235+
: quotesOnKeys ? <>&quot;{key}&quot;</> : <>{key}</>
224236
)
225237
}
226238
{

src/components/DataTypes/Object.tsx

+14-2
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export const ObjectType: React.FC<DataItemProps<object>> = (props) => {
112112
const [displayLength, setDisplayLength] = useState(
113113
useJsonViewerStore(store => store.maxDisplayLength)
114114
)
115+
const objectSortKeys = useJsonViewerStore(store => store.objectSortKeys)
115116
const elements = useMemo(() => {
116117
if (!props.inspect) {
117118
return null
@@ -188,7 +189,16 @@ export const ObjectType: React.FC<DataItemProps<object>> = (props) => {
188189
})
189190
} else {
190191
// object
191-
const entries = Object.entries(value)
192+
let entries: [key: string, value: unknown][] = Object.entries(value)
193+
if (objectSortKeys) {
194+
entries = entries.sort(([a], [b]) => {
195+
if (objectSortKeys === true) {
196+
return a.localeCompare(b)
197+
} else {
198+
return objectSortKeys(a, b)
199+
}
200+
})
201+
}
192202
const elements = entries.slice(0, displayLength).map(([key, value]) => {
193203
const path = [...props.path, key]
194204
return (
@@ -221,7 +231,9 @@ export const ObjectType: React.FC<DataItemProps<object>> = (props) => {
221231
props.path,
222232
groupArraysAfterLength,
223233
displayLength,
224-
keyColor])
234+
keyColor,
235+
objectSortKeys
236+
])
225237
const marginLeft = props.inspect ? 0.6 : 0
226238
const width = useJsonViewerStore(store => store.indentWidth)
227239
const indentWidth = props.inspect ? width - marginLeft : width

src/stores/JsonViewerStore.ts

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export type JsonViewerState<T = unknown> = {
2020
maxDisplayLength: number
2121
defaultInspectDepth: number
2222
collapseStringsAfterLength: number
23+
objectSortKeys: boolean | ((a: string, b: string) => number)
24+
quotesOnKeys: boolean
2325
colorspace: Colorspace
2426
editable: boolean | (<U>(path: Path, currentValue: U) => boolean)
2527
rootName: false | string
@@ -50,6 +52,8 @@ export const createJsonViewerStore = <T = unknown>(props: JsonViewerProps<T>) =>
5052
keyRenderer: props.keyRenderer ?? DefaultKeyRenderer,
5153
editable: props.editable ?? true,
5254
defaultInspectDepth: props.defaultInspectDepth ?? 5,
55+
objectSortKeys: props.objectSortKeys ?? false,
56+
quotesOnKeys: props.quotesOnKeys ?? true,
5357
// internal state
5458
inspectCache: {},
5559
hoverPath: null,

src/type.ts

+17
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,23 @@ export type JsonViewerProps<T = unknown> = {
107107
* @default 100
108108
*/
109109
collapseStringsAfterLength?: number
110+
111+
/**
112+
* Whether sort keys through `String.prototype.localeCompare()`
113+
*
114+
* @default false
115+
*/
116+
objectSortKeys?: boolean | ((a: string, b: string) => number)
117+
118+
/**
119+
* set `false` to remove quotes from keys
120+
*
121+
* true for `"name"`, false for `name`
122+
*
123+
* @default true
124+
*/
125+
quotesOnKeys?: boolean
126+
110127
className?: string
111128
style?: React.CSSProperties
112129
/**

tests/index.test.tsx

+75
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,61 @@ import { describe, it } from 'vitest'
44

55
import { JsonViewer } from '../src'
66

7+
function aPlusB (a: number, b: number) {
8+
return a + b
9+
}
10+
11+
const loopObject = {
12+
foo: 1,
13+
goo: 'string'
14+
} as Record<string, any>
15+
16+
loopObject.self = loopObject
17+
18+
const loopArray = [
19+
loopObject
20+
]
21+
22+
loopArray[1] = loopArray
23+
24+
const longArray = Array.from({ length: 1000 }).map((_, i) => i)
25+
const map = new Map<any, any>()
26+
map.set('foo', 1)
27+
map.set('goo', 'hello')
28+
map.set({}, 'world')
29+
30+
const set = new Set([1, 2, 3])
31+
32+
const superLongString = '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'
33+
34+
const full = {
35+
loopObject,
36+
loopArray,
37+
longArray,
38+
string: 'this is a string',
39+
integer: 42,
40+
array: [19, 19, 810, 'test', NaN],
41+
nestedArray: [
42+
[1, 2],
43+
[3, 4]
44+
],
45+
map,
46+
set,
47+
float: 114.514,
48+
undefined,
49+
superLongString,
50+
object: {
51+
'first-child': true,
52+
'second-child': false,
53+
'last-child': null
54+
},
55+
fn: aPlusB,
56+
string_number: '1234',
57+
timer: 0,
58+
date: new Date('Tue Sep 13 2022 14:07:44 GMT-0500 (Central Daylight Time)'),
59+
bigint: 110101195306153019n
60+
}
61+
762
describe('render <JsonViewer/>', () => {
863
it('render undefined', () => {
964
render(<JsonViewer value={undefined}/>)
@@ -71,4 +126,24 @@ describe('render <JsonViewer/>', () => {
71126
}}/>)
72127
render(<JsonViewer value={(a: number, b: number) => a + b}/>)
73128
})
129+
130+
it('render full', () => {
131+
render(<JsonViewer value={full}/>)
132+
})
133+
})
134+
135+
describe('render <JsonViewer/> with props', () => {
136+
it('render with quotesOnKeys', () => {
137+
const selection = [true, false]
138+
selection.forEach(quotesOnKeys => {
139+
render(<JsonViewer value={full} quotesOnKeys={quotesOnKeys}/>)
140+
})
141+
})
142+
143+
it('render with objectSortKeys', () => {
144+
const selection = [true, false, (a: string, b: string) => a.localeCompare(b)]
145+
selection.forEach(objectSortKeys => {
146+
render(<JsonViewer value={full} objectSortKeys={objectSortKeys}/>)
147+
})
148+
})
74149
})

0 commit comments

Comments
 (0)