Skip to content

Commit 324ae98

Browse files
authored
Merge pull request #31 from AndyOGo/bugfix/quote-json-proptery-names
fix: always quote objects property names
2 parents 2518853 + efd5318 commit 324ae98

File tree

4 files changed

+89
-56
lines changed

4 files changed

+89
-56
lines changed

src/DataRenderer.test.tsx

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,19 @@ const testClickableNodeCollapsed = () => {
6666
describe('DataRender', () => {
6767
it('should render booleans: true', () => {
6868
render(<DataRender {...commonProps} value={{ test: true }} />);
69-
expect(screen.getByText(/test/)).toBeInTheDocument();
69+
expect(screen.getByText(/test:/)).toBeInTheDocument();
7070
expect(screen.getByText('true')).toBeInTheDocument();
7171
});
7272

7373
it('should render booleans: false', () => {
7474
render(<DataRender {...commonProps} value={{ test: false }} />);
75-
expect(screen.getByText(/test/)).toBeInTheDocument();
75+
expect(screen.getByText(/test:/)).toBeInTheDocument();
7676
expect(screen.getByText('false')).toBeInTheDocument();
7777
});
7878

7979
it('should render strings', () => {
8080
render(<DataRender {...commonProps} value={{ test: 'string' }} />);
81-
expect(screen.getByText(/test/)).toBeInTheDocument();
81+
expect(screen.getByText(/test:/)).toBeInTheDocument();
8282
expect(screen.getByText(`"string"`)).toBeInTheDocument();
8383
});
8484

@@ -90,7 +90,7 @@ describe('DataRender', () => {
9090
value={{ test: 'string' }}
9191
/>
9292
);
93-
expect(screen.getByText(/test/)).toBeInTheDocument();
93+
expect(screen.getByText(/test:/)).toBeInTheDocument();
9494
expect(screen.getByText(`string`)).toBeInTheDocument();
9595
expect(screen.queryByText(`"string"`)).not.toBeInTheDocument();
9696
});
@@ -103,49 +103,71 @@ describe('DataRender', () => {
103103
value={{ test: 'string' }}
104104
/>
105105
);
106-
expect(screen.getByText(/test/)).toBeInTheDocument();
106+
expect(screen.getByText(/test:/)).toBeInTheDocument();
107107
expect(screen.getByText(`"string"`)).toBeInTheDocument();
108108
});
109109

110+
it('should render field names without quotes if quotesForFieldNames is undefined', () => {
111+
render(
112+
<DataRender
113+
{...commonProps}
114+
style={{ ...defaultStyles, quotesForFieldNames: undefined }}
115+
value={{ test: 'string' }}
116+
/>
117+
);
118+
expect(screen.getByText(/test:/)).toBeInTheDocument();
119+
});
120+
121+
it('should render field names with quotes if quotesForFieldNames is true', () => {
122+
render(
123+
<DataRender
124+
{...commonProps}
125+
style={{ ...defaultStyles, quotesForFieldNames: true }}
126+
value={{ test: 'string' }}
127+
/>
128+
);
129+
expect(screen.getByText(/"test":/)).toBeInTheDocument();
130+
});
131+
110132
it('should render numbers', () => {
111133
render(<DataRender {...commonProps} value={{ test: 42 }} />);
112-
expect(screen.getByText(/test/)).toBeInTheDocument();
134+
expect(screen.getByText(/test:/)).toBeInTheDocument();
113135
expect(screen.getByText('42')).toBeInTheDocument();
114136
});
115137

116138
it('should render bigints', () => {
117139
render(<DataRender {...commonProps} value={{ test: BigInt(42) }} />);
118-
expect(screen.getByText(/test/)).toBeInTheDocument();
140+
expect(screen.getByText(/test:/)).toBeInTheDocument();
119141
expect(screen.getByText('42n')).toBeInTheDocument();
120142
});
121143

122144
it('should render dates', () => {
123145
render(<DataRender {...commonProps} value={{ test: new Date(0) }} />);
124-
expect(screen.getByText(/test/)).toBeInTheDocument();
146+
expect(screen.getByText(/test:/)).toBeInTheDocument();
125147
expect(screen.getByText('1970-01-01T00:00:00.000Z')).toBeInTheDocument();
126148
});
127149

128150
it('should render nulls', () => {
129151
render(<DataRender {...commonProps} value={{ test: null }} />);
130-
expect(screen.getByText(/test/)).toBeInTheDocument();
152+
expect(screen.getByText(/test:/)).toBeInTheDocument();
131153
expect(screen.getByText('null')).toBeInTheDocument();
132154
});
133155

134156
it('should render undefineds', () => {
135157
render(<DataRender {...commonProps} value={{ test: undefined }} />);
136-
expect(screen.getByText(/test/)).toBeInTheDocument();
158+
expect(screen.getByText(/test:/)).toBeInTheDocument();
137159
expect(screen.getByText('undefined')).toBeInTheDocument();
138160
});
139161

140162
it('should render unknown types', () => {
141163
render(<DataRender {...commonProps} value={{ test: Symbol('2020') }} />);
142-
expect(screen.getByText(/test/)).toBeInTheDocument();
164+
expect(screen.getByText(/test:/)).toBeInTheDocument();
143165
expect(screen.getByText(/2020/)).toBeInTheDocument();
144166
});
145167

146168
it('should render object with empty key string', () => {
147169
render(<DataRender {...commonProps} value={{ '': 'empty key' }} />);
148-
expect(screen.getByText(/""/)).toBeInTheDocument();
170+
expect(screen.getByText(/"":/)).toBeInTheDocument();
149171
expect(screen.getByText(/empty key/)).toBeInTheDocument();
150172
});
151173

@@ -211,7 +233,7 @@ describe('DataRender', () => {
211233

212234
it('should render nested objects', () => {
213235
render(<DataRender {...commonProps} value={{ obj: { test: 123 } }} />);
214-
expect(screen.getByText(/test/)).toBeInTheDocument();
236+
expect(screen.getByText(/test:/)).toBeInTheDocument();
215237
expect(screen.getByText('123')).toBeInTheDocument();
216238
});
217239

@@ -224,7 +246,7 @@ describe('DataRender', () => {
224246
/>
225247
);
226248
expect(screen.getByText(/obj/)).toBeInTheDocument();
227-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
249+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
228250
expect(screen.queryByText('123')).not.toBeInTheDocument();
229251
});
230252

@@ -237,36 +259,36 @@ describe('DataRender', () => {
237259
/>
238260
);
239261
expect(screen.getByText(/obj/)).toBeInTheDocument();
240-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
262+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
241263
expect(screen.queryByText('123')).not.toBeInTheDocument();
242264

243265
rerender(
244266
<DataRender {...commonProps} value={{ obj: { test: 123 } }} shouldExpandNode={allExpanded} />
245267
);
246268
expect(screen.getByText(/obj/)).toBeInTheDocument();
247-
expect(screen.queryByText(/test/)).toBeInTheDocument();
269+
expect(screen.queryByText(/test:/)).toBeInTheDocument();
248270
expect(screen.queryByText('123')).toBeInTheDocument();
249271
});
250272

251273
it('should render nested arrays collapsed', () => {
252274
render(
253275
<DataRender {...commonProps} value={{ test: [123] }} shouldExpandNode={collapseAllNested} />
254276
);
255-
expect(screen.queryByText(/test/)).toBeInTheDocument();
277+
expect(screen.queryByText(/test:/)).toBeInTheDocument();
256278
expect(screen.queryByText('123')).not.toBeInTheDocument();
257279
});
258280

259281
it('should render nested arrays collapsed and expand it once property changed', () => {
260282
const { rerender } = render(
261283
<DataRender {...commonProps} value={{ test: [123] }} shouldExpandNode={collapseAllNested} />
262284
);
263-
expect(screen.queryByText(/test/)).toBeInTheDocument();
285+
expect(screen.queryByText(/test:/)).toBeInTheDocument();
264286
expect(screen.queryByText('123')).not.toBeInTheDocument();
265287

266288
rerender(
267289
<DataRender {...commonProps} value={{ test: [123] }} shouldExpandNode={allExpanded} />
268290
);
269-
expect(screen.queryByText(/test/)).toBeInTheDocument();
291+
expect(screen.queryByText(/test:/)).toBeInTheDocument();
270292
expect(screen.queryByText('123')).toBeInTheDocument();
271293
});
272294

@@ -277,13 +299,13 @@ describe('DataRender', () => {
277299

278300
it('should collapse and expand objects by clicking on icon', () => {
279301
render(<DataRender {...commonProps} value={{ test: true }} />);
280-
expect(screen.getByText(/test/)).toBeInTheDocument();
302+
expect(screen.getByText(/test:/)).toBeInTheDocument();
281303
let buttons = testButtonsExpanded();
282304
fireEvent.click(buttons[0]);
283-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
305+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
284306
buttons = testButtonsCollapsed();
285307
fireEvent.click(buttons[0]);
286-
expect(screen.getByText(/test/)).toBeInTheDocument();
308+
expect(screen.getByText(/test:/)).toBeInTheDocument();
287309
});
288310

289311
it('should collapse and expand objects by clicking on node', () => {
@@ -296,25 +318,25 @@ describe('DataRender', () => {
296318
);
297319

298320
// open the 'test' node by clicking the icon
299-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
321+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
300322
expect(screen.queryByText(/child/)).not.toBeInTheDocument();
301323
const buttons = testButtonsCollapsed();
302324
fireEvent.click(buttons[0]);
303325
testClickableNodeCollapsed();
304-
expect(screen.getByText(/test/)).toBeInTheDocument();
326+
expect(screen.getByText(/test:/)).toBeInTheDocument();
305327
expect(screen.queryByText(/child/)).not.toBeInTheDocument();
306328
fireEvent.click(buttons[0]);
307-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
329+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
308330
expect(screen.queryByText(/child/)).not.toBeInTheDocument();
309331
});
310332

311333
it('should expand objects by clicking on collapsed content', () => {
312334
render(<DataRender {...commonProps} value={{ test: true }} shouldExpandNode={collapseAll} />);
313-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
335+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
314336
const buttons = testButtonsCollapsed();
315337
fireEvent.click(buttons[1]);
316338
testButtonsExpanded();
317-
expect(screen.getByText(/test/)).toBeInTheDocument();
339+
expect(screen.getByText(/test:/)).toBeInTheDocument();
318340
});
319341

320342
it('should collapse and expand arrays by clicking on icon', () => {
@@ -339,20 +361,20 @@ describe('DataRender', () => {
339361

340362
it('should expand objects by pressing Spacebar on icon', () => {
341363
render(<DataRender {...commonProps} value={{ test: true }} shouldExpandNode={collapseAll} />);
342-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
364+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
343365
const buttons = testButtonsCollapsed();
344366
fireEvent.keyDown(buttons[0], { key: ' ', code: 'Space' });
345367
testButtonsExpanded();
346-
expect(screen.getByText(/test/)).toBeInTheDocument();
368+
expect(screen.getByText(/test:/)).toBeInTheDocument();
347369
});
348370

349371
it('should not expand objects by pressing other keys on icon', () => {
350372
render(<DataRender {...commonProps} value={{ test: true }} shouldExpandNode={collapseAll} />);
351-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
373+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
352374
const buttons = testButtonsCollapsed();
353375
fireEvent.keyDown(buttons[0], { key: 'Enter', code: 'Enter' });
354376
testButtonsCollapsed();
355-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
377+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
356378
});
357379

358380
it('should expand arrays by pressing Spacebar on icon', () => {
@@ -373,11 +395,11 @@ describe('DataRender', () => {
373395
<DataRender {...commonProps} value={['test', 'array']} shouldExpandNode={collapseAll} />
374396
);
375397
const buttons = testButtonsCollapsed();
376-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
398+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
377399
expect(screen.queryByText(/array/)).not.toBeInTheDocument();
378400
fireEvent.keyDown(buttons[0], { key: 'Enter', code: 'Enter' });
379401
testButtonsCollapsed();
380-
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
402+
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
381403
expect(screen.queryByText(/array/)).not.toBeInTheDocument();
382404
});
383405
});

src/DataRenderer.tsx

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export interface StyleProps {
1717
expandIcon: string;
1818
collapseIcon: string;
1919
collapsedContent: string;
20-
noQuotesForStringValues: boolean;
20+
noQuotesForStringValues?: boolean;
21+
quotesForFieldNames?: boolean;
2122
}
2223

2324
export interface JsonRenderProps<T> {
@@ -43,6 +44,14 @@ export interface ExpandableRenderProps {
4344
clickToExpandNode: boolean;
4445
}
4546

47+
function quoteString(value: string, quoted = false) {
48+
if (!value || quoted) {
49+
return `"${value}"`;
50+
}
51+
52+
return value;
53+
}
54+
4655
function ExpandableObject({
4756
field,
4857
value,
@@ -94,7 +103,7 @@ function ExpandableObject({
94103
aria-expanded={expanded}
95104
aria-controls={expanded ? contentsId : undefined}
96105
/>
97-
{field &&
106+
{(field || field === '') &&
98107
(clickToExpandNode ? (
99108
<span
100109
className={style.clickableLabel}
@@ -103,10 +112,10 @@ function ExpandableObject({
103112
role='button'
104113
tabIndex={-1}
105114
>
106-
{field}:
115+
{quoteString(field, style.quotesForFieldNames)}:
107116
</span>
108117
) : (
109-
<span className={style.label}>{field}:</span>
118+
<span className={style.label}>{quoteString(field, style.quotesForFieldNames)}:</span>
110119
))}
111120
<span className={style.punctuation}>{openBracket}</span>
112121

@@ -155,7 +164,9 @@ export interface EmptyRenderProps {
155164
function EmptyObject({ field, openBracket, closeBracket, lastElement, style }: EmptyRenderProps) {
156165
return (
157166
<div className={style.basicChildStyle} role='listitem'>
158-
{field && <span className={style.label}>{field}:</span>}
167+
{(field || field === '') && (
168+
<span className={style.label}>{quoteString(field, style.quotesForFieldNames)}:</span>
169+
)}
159170
<span className={style.punctuation}>{openBracket}</span>
160171
<span className={style.punctuation}>{closeBracket}</span>
161172
{!lastElement && <span className={style.punctuation}>,</span>}
@@ -235,7 +246,7 @@ function JsonPrimitiveValue({
235246
style,
236247
lastElement
237248
}: JsonRenderProps<string | number | boolean | Date | null | undefined>) {
238-
let stringValue = value;
249+
let stringValue;
239250
let valueStyle = style.otherValue;
240251

241252
if (value === null) {
@@ -245,7 +256,7 @@ function JsonPrimitiveValue({
245256
stringValue = 'undefined';
246257
valueStyle = style.undefinedValue;
247258
} else if (DataTypeDetection.isString(value)) {
248-
stringValue = style.noQuotesForStringValues ? (value as string) : `"${value}"`;
259+
stringValue = quoteString(value, !style.noQuotesForStringValues);
249260
valueStyle = style.stringValue;
250261
} else if (DataTypeDetection.isBoolean(value)) {
251262
stringValue = value ? 'true' : 'false';
@@ -259,16 +270,14 @@ function JsonPrimitiveValue({
259270
} else if (DataTypeDetection.isDate(value)) {
260271
stringValue = value.toISOString();
261272
} else {
262-
stringValue = value.toString();
263-
}
264-
265-
if (field === '') {
266-
field = '""';
273+
stringValue = (value as any).toString();
267274
}
268275

269276
return (
270277
<div className={style.basicChildStyle} role='listitem'>
271-
{field && <span className={style.label}>{field}:</span>}
278+
{(field || field === '') && (
279+
<span className={style.label}>{quoteString(field, style.quotesForFieldNames)}:</span>
280+
)}
272281
<span className={valueStyle}>{stringValue}</span>
273282
{!lastElement && <span className={style.punctuation}>,</span>}
274283
</div>

src/DataTypeDetection.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
export const isBoolean = (data: any) => {
1+
export const isBoolean = (data: any): data is boolean => {
22
return typeof data === 'boolean' || data instanceof Boolean;
33
};
44

5-
export const isNumber = (data: any) => {
5+
export const isNumber = (data: any): data is number => {
66
return typeof data === 'number' || data instanceof Number;
77
};
88

9-
export const isBigInt = (data: any) => {
9+
export const isBigInt = (data: any): data is BigInt => {
1010
return typeof data === 'bigint' || data instanceof BigInt;
1111
};
1212

1313
export const isDate = (data: unknown): data is Date => {
1414
return !!data && data instanceof Date;
1515
};
1616

17-
export const isString = (data: any) => {
17+
export const isString = (data: any): data is string => {
1818
return typeof data === 'string' || data instanceof String;
1919
};
2020

21-
export const isArray = (data: any) => {
21+
export const isArray = (data: any): data is Array<any> => {
2222
return Array.isArray(data);
2323
};
2424

25-
export const isObject = (data: any) => {
25+
export const isObject = (data: any): data is object => {
2626
return data instanceof Object && data !== null;
2727
};
2828

29-
export const isNull = (data: any) => {
29+
export const isNull = (data: any): data is null => {
3030
return data === null;
3131
};
3232

33-
export const isUndefined = (data: any) => {
33+
export const isUndefined = (data: any): data is undefined => {
3434
return data === undefined;
3535
};

0 commit comments

Comments
 (0)