Skip to content

Commit c79c3b2

Browse files
committed
collapse objects
1 parent 7f06d00 commit c79c3b2

File tree

2 files changed

+86
-19
lines changed

2 files changed

+86
-19
lines changed

src/components/Json/Json.stories.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const Arrays: Story = {
3636
numbers1: Array.from({ length: 1 }, (_, i) => i),
3737
numbers8: Array.from({ length: 8 }, (_, i) => i),
3838
numbers100: Array.from({ length: 100 }, (_, i) => i),
39+
strings2: Array.from({ length: 2 }, (_, i) => `hello ${i}`),
3940
strings8: Array.from({ length: 8 }, (_, i) => `hello ${i}`),
4041
strings100: Array.from({ length: 100 }, (_, i) => `hello ${i}`),
4142
misc: Array.from({ length: 8 }, (_, i) => i % 2 ? `hello ${i}` : i),
@@ -46,3 +47,21 @@ export const Arrays: Story = {
4647
},
4748
render,
4849
}
50+
51+
export const Objects: Story = {
52+
args: {
53+
json: {
54+
empty: {},
55+
numbers1: { k0: 1 },
56+
numbers8: Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`k${i}`, i])),
57+
numbers100: Object.fromEntries(Array.from({ length: 100 }, (_, i) => [`k${i}`, i])),
58+
strings8: Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`k${i}`, `hello ${i}`])),
59+
strings100: Object.fromEntries(Array.from({ length: 100 }, (_, i) => [`k${i}`, `hello ${i}`])),
60+
misc: Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`k${i}`, i % 2 ? `hello ${i}` : i])),
61+
misc2: Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`k${i}`, i % 3 === 0 ? i : i % 3 === 1 ? `hello ${i}` : [i, i + 1, i + 2]])),
62+
arrays100: Object.fromEntries(Array.from({ length: 100 }, (_, i) => [`k${i}`, [i, i + 1, i + 2]])),
63+
},
64+
label: 'json',
65+
},
66+
render,
67+
}

src/components/Json/Json.tsx

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,6 @@ function isPrimitive(value: unknown): boolean {
4545
)
4646
}
4747

48-
function InlineArray({ suffix, children }: {suffix?: string, children?: ReactNode}): ReactNode {
49-
return (
50-
<>
51-
<span className={styles.array}>{'['}</span>
52-
<span className={styles.array}>{children}</span>
53-
<span className={styles.array}>{']'}</span>
54-
{suffix && <span className={styles.comment}>{suffix}</span>}
55-
</>
56-
)
57-
}
58-
5948
function CollapsedArray({ array }: {array: unknown[]}): ReactNode {
6049
const maxCharacterCount = 40
6150
const separator = ', '
@@ -64,17 +53,17 @@ function CollapsedArray({ array }: {array: unknown[]}): ReactNode {
6453
let suffix: string | undefined = undefined
6554

6655
let characterCount = 0
67-
for (const [index, item] of array.entries()) {
56+
for (const [index, value] of array.entries()) {
6857
if (index > 0) {
6958
characterCount += separator.length
70-
children.push(<span key={`separator-${index - 1}`}>, </span>)
59+
children.push(<span key={`separator-${index - 1}`}>{separator}</span>)
7160
}
7261
// should we continue?
73-
if (isPrimitive(item)) {
74-
const asString = typeof item === 'bigint' ? item.toString() : JSON.stringify(item)
62+
if (isPrimitive(value)) {
63+
const asString = typeof value === 'bigint' ? value.toString() : JSON.stringify(value)
7564
characterCount += asString.length
7665
if (characterCount < maxCharacterCount) {
77-
children.push(<JsonContent json={item} key={`item-${index}`} />)
66+
children.push(<JsonContent json={value} key={`value-${index}`} />)
7867
continue
7968
}
8069
}
@@ -83,7 +72,14 @@ function CollapsedArray({ array }: {array: unknown[]}): ReactNode {
8372
suffix = ` length: ${array.length}`
8473
break
8574
}
86-
return <InlineArray suffix={suffix}>{children}</InlineArray>
75+
return (
76+
<>
77+
<span className={styles.array}>{'['}</span>
78+
<span className={styles.array}>{children}</span>
79+
<span className={styles.array}>{']'}</span>
80+
{suffix && <span className={styles.comment}>{suffix}</span>}
81+
</>
82+
)
8783
}
8884

8985
function JsonArray({ array, label }: { array: unknown[], label?: string }): ReactNode {
@@ -109,14 +105,66 @@ function JsonArray({ array, label }: { array: unknown[], label?: string }): Reac
109105
</>
110106
}
111107

108+
function CollapsedObject({ obj }: {obj: object}): ReactNode {
109+
const maxCharacterCount = 40
110+
const separator = ', '
111+
const kvSeparator = ': '
112+
113+
const children: ReactNode[] = []
114+
let suffix: string | undefined = undefined
115+
116+
const entries = Object.entries(obj)
117+
let characterCount = 0
118+
for (const [index, [key, value]] of entries.entries()) {
119+
if (index > 0) {
120+
characterCount += separator.length
121+
children.push(<span key={`separator-${index - 1}`}>{separator}</span>)
122+
}
123+
// should we continue?
124+
if (isPrimitive(value)) {
125+
const asString = typeof value === 'bigint' ? value.toString() : JSON.stringify(value)
126+
characterCount += key.length + kvSeparator.length + asString.length
127+
if (characterCount < maxCharacterCount) {
128+
children.push(<JsonContent json={value as unknown} label={key} key={`value-${index}`} />)
129+
continue
130+
}
131+
}
132+
// no: it was the last entry
133+
children.push(<span key="rest">...</span>)
134+
suffix = ` entries: ${entries.length}`
135+
break
136+
}
137+
return (
138+
<>
139+
<span className={styles.object}>{'{'}</span>
140+
<span className={styles.object}>{children}</span>
141+
<span className={styles.object}>{'}'}</span>
142+
{suffix && <span className={styles.comment}>{suffix}</span>}
143+
</>
144+
)
145+
}
146+
147+
function shouldCollapse(obj: object): boolean {
148+
const values = Object.values(obj)
149+
if (
150+
// if all the values are primitive
151+
values.every(value => isPrimitive(value))
152+
// if the object has too many entries
153+
|| values.length >= 100
154+
) {
155+
return true
156+
}
157+
return false
158+
}
159+
112160
function JsonObject({ obj, label }: { obj: object, label?: string }): ReactNode {
113-
const [collapsed, setCollapsed] = useState(false)
161+
const [collapsed, setCollapsed] = useState(shouldCollapse(obj))
114162
const key = label ? <span className={styles.key}>{label}: </span> : ''
115163
if (collapsed) {
116164
return <div className={styles.clickable} onClick={() => { setCollapsed(false) }}>
117165
<span className={styles.drill}>{'\u25B6'}</span>
118166
{key}
119-
<span className={styles.object}>{'{...}'}</span>
167+
<CollapsedObject obj={obj}></CollapsedObject>
120168
</div>
121169
}
122170
return <>

0 commit comments

Comments
 (0)