Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Job args: preserve large numeric JSON values exactly when displaying and copying args, while keeping object keys sorted. [Fixes #593](https://github.com/riverqueue/riverui/issues/593). [PR #594](https://github.com/riverqueue/riverui/pull/594).
- JSON viewer: render empty and escaped object property keys as valid JSON strings. [PR #632](https://github.com/riverqueue/riverui/pull/632).

## [v0.17.0] - 2026-07-31

Expand Down
13 changes: 13 additions & 0 deletions src/components/JSONView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@ describe("JSONView Component", () => {
expect(bravoBeforeZulu).toBeTruthy();
});

it("renders empty and escaped object keys as valid JSON strings", () => {
const data = JSON.parse(
String.raw`{"":"empty key","quote\"key":"quoted key"}`,
) as Record<string, unknown>;

render(<JSONView data={data} />);

expect(screen.getByText('""')).toBeInTheDocument();
expect(screen.getByText(String.raw`"quote\"key"`)).toBeInTheDocument();
expect(screen.getByText('"empty key"')).toBeInTheDocument();
expect(screen.getByText('"quoted key"')).toBeInTheDocument();
});

it("renders nested JSON data with collapsed nodes but visible keys by default", () => {
render(<JSONView data={nestedData} defaultExpandDepth={1} />);

Expand Down
10 changes: 5 additions & 5 deletions src/components/JSONView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function JSONNodeRenderer({
const color = styleConfig.json.key;

// For primitive values, render key and value inline
if (propKey && isPrimitiveValue(data)) {
if (propKey !== null && isPrimitiveValue(data)) {
return (
<span
style={{
Expand All @@ -191,7 +191,7 @@ function JSONNodeRenderer({
whiteSpace: "nowrap",
}}
>
<span className={styleConfig.json.key}>&quot;{propKey}&quot;</span>
<span className={styleConfig.json.key}>{JSON.stringify(propKey)}</span>
<span className={styleConfig.json.key}>:&nbsp;</span>
{typeof data === "string" ? (
<span className={valueColor("string")} style={{ whiteSpace: "pre" }}>
Expand All @@ -213,7 +213,7 @@ function JSONNodeRenderer({
}

// For non-primitive values (objects/arrays) or non-property values, use the normal rendering
if (!propKey) {
if (propKey === null) {
return renderValue(
data,
valueColor,
Expand All @@ -234,7 +234,7 @@ function JSONNodeRenderer({
if (count === 0) {
return (
<span>
<span style={{ color }}>&quot;{propKey}&quot;</span>
<span style={{ color }}>{JSON.stringify(propKey)}</span>
<span style={{ color }}>:&nbsp;</span>
<span>
{isArray ? "[]" : "{}"}
Expand Down Expand Up @@ -294,7 +294,7 @@ function JSONNodeRenderer({
/>
)}
</span>
<span style={{ color }}>&quot;{propKey}&quot;</span>
<span style={{ color }}>{JSON.stringify(propKey)}</span>
<span style={{ color }}>:&nbsp;</span>
<span>{isArray ? "[" : "{"}</span>
{!open && (
Expand Down
Loading