Skip to content

fix(cli): preserve nested table values - #86

Open
ThomasRooney wants to merge 5 commits into
mainfrom
fix/cli-table-nested-fields
Open

ThomasRooney wants to merge 5 commits into
mainfrom
fix/cli-table-nested-fields

Conversation

@ThomasRooney

@ThomasRooney ThomasRooney commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Why

Single-object table output omitted nested objects and maps. Arrays rendered as sections could show a row's scalar fields while dropping its nested payload.

What changed

Single objects render as FIELD/VALUE tables with JSON field names. Nested objects and maps occupy compact JSON cells. Arrays of objects render as titled tables with one row per entry, including columns for nested payloads.

Root custom marshalers define the displayed fields, including flattened additional properties. Section columns come from each row's JSON representation, preserving custom marshalers and generated union wire shapes without exposing internal discriminator fields. Columns introduced by later rows are included, omitted fields have blank cells, and nested arrays use compact JSON. Top-level primitive arrays retain comma-separated cells. This also applies to list wrappers and whole-page pagination without a declared results path. Direct array tables and declared-results aggregation retain their existing columns, with control characters now escaped in row cells. Map keys and values also escape control characters.

Single-object tables exclude JSON-hidden storage fields, escape control characters, and preserve exact JSON numbers. Custom-serialized strings, including dates, display without JSON quotes. Serialization errors include object, field, or row context and do not emit a partial single-result table. Streaming uses the same layout for each event. JSON, YAML, TOON, and pretty output are unchanged.

The change includes renderer regressions, runtime coverage using generated union models, regenerated review output, documentation, and a patch changeset.

Example

For this single-object response:

{
  "id": "widget-1",
  "metadata": {"owner": "demo"},
  "entries": [
    {"kind": "note", "payload": {"text": "hello"}}
  ]
}

Before, --output-format table omitted the metadata and the entry's payload:

ID  widget-1

ENTRIES
  KIND
  note

After:

FIELD     VALUE
id        widget-1
metadata  {"owner":"demo"}

ENTRIES
KIND  PAYLOAD
note  {"text":"hello"}

The nested payload stays in the entry's row, under PAYLOAD. Both outputs were captured from the renderer using the same synthetic typed response.

Testing

  • Full generated suites passed on 2d69199b: 651 review tests passed with 2 skips; all 1,705 primary tests passed.
  • The scalar-array follow-up passed focused generated review/primary renderer and table tests, an exact generated BaseUser before/after regression, and regenerated CLI checks/output comparisons.
  • Both suites passed with isolated mock-server ports after the shared-port runs encountered mock-server 404s and connection refusals.
  • Renderer regressions and generated Metadata, Vehicle, and Date reproductions fail before the fix and pass afterward.
  • Regenerated CLI non-live validation passed. Mocked single-object, list, pagination, and streaming checks passed; JSON/YAML/TOON/pretty output and bounded help/usage comparisons were unchanged.
  • Formatting, template checks, full repository lint, and git diff --check passed.

Public-safety check

  • This change contains no credentials, customer documents, private repository URLs, private filesystem paths, or unredacted private logs.
  • Title, body, comments, and commit messages name no customers or customer-derived identifiers, private paths or trackers, or workflow provenance, and are understandable without private context.
  • Generated fixtures and review SDK changes are public-safe.
  • I reviewed git diff --check.

@ThomasRooney
ThomasRooney requested a review from a team as a code owner October 7, 2026 15:20

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 11 files

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread zSDKs/sdk-cli/internal/output/output.go
Comment thread templates/templates/cli/auxiliary/internal/output/output.go.stmpl
Comment thread templates/templates/cli/README.md Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 6 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would auto-approve. Focused, well-tested renderer fix restores previously omitted nested struct/map/array fields in single-object and streaming table output as compact JSON, with wire-name and custom-marshaler handling preserved; machine-format output and existing section columns remain unchanged.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread templates/templates/cli/auxiliary/internal/output/output.go.stmpl

@2ynn 2ynn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed; no blocking findings.

@AshGodfrey

Copy link
Copy Markdown
Contributor

Issues;

  • AdditionalProperties dropped at the top level. A Metadata object with allergies plus extra keys renders only the allergies row. JSON output shows the extra keys. This is silent data loss, not an error.
  • Row and map tables still break on tabs and newlines. The single-object view escapes them. The list view of the same struct shifts columns and splits rows. This was pre-existing, but the PR now makes the two views disagree.
  • Union sections show a TYPE column that is not on the wire. A Vehicle section prints a Car type column while the detail line and JSON output have no type key. Unknown variants print UNKNOWN.

Minor:

  • Zero-value nested structs print a cell of zero timestamps. Only required nested objects are affected because optional ones are pointers.
  • The generated Date type renders quoted in the object view. In row tables it was dropped before and still is. The PR makes it appear, just with quotes.
  • The marshaler reflect type is rebuilt in four places.

@2ynn

2ynn commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Issues;

  • AdditionalProperties dropped at the top level. A Metadata object with allergies plus extra keys renders only the allergies row. JSON output shows the extra keys. This is silent data loss, not an error.
  • Row and map tables still break on tabs and newlines. The single-object view escapes them. The list view of the same struct shifts columns and splits rows. This was pre-existing, but the PR now makes the two views disagree.
  • Union sections show a TYPE column that is not on the wire. A Vehicle section prints a Car type column while the detail line and JSON output have no type key. Unknown variants print UNKNOWN.

Minor:

  • Zero-value nested structs print a cell of zero timestamps. Only required nested objects are affected because optional ones are pointers.
  • The generated Date type renders quoted in the object view. In row tables it was dropped before and still is. The PR makes it appear, just with quotes.
  • The marshaler reflect type is rebuilt in four places.

The first three are legitimate issues with actual generated types, although all three predate PR #86:

  1. AdditionalProperties omitted — P2.
    Agreed: successful output silently omits response data. The important distinction is that json:"-" hides the storage field, not its contents: the generated Metadata.MarshalJSON flattens those entries into the wire object. The table renderer misses that distinction.
    Before this PR, it skipped the field because it was a map; afterward, it skips it because of json:"-". This is an existing bug left unresolved by a closely related fix. It deserves a generated-response regression test and a fix. Metadata definition.
  2. Tabs/newlines break row and map tables — P2 follow-up.
    Agreed. This corrupts the table’s structure, rather than merely looking inconsistent. However, the PR improves single-object rendering without worsening the existing row/map paths. I’d extend escaping to those paths, including map keys, in a separate fix.
  3. Union sections expose an internal TYPE column — valid, lower priority.
    Vehicle.Type is an SDK bookkeeping field; its custom serializer emits the selected vehicle without that field. Showing it alongside the new JSON detail creates a misleading mixture of internal and wire representations. The column already existed, though.
    The fix should recognize generated union metadata or derive columns from serialized content. Simply hiding every field named Type would incorrectly hide legitimate API properties. Vehicle serializer.
    For the minor points:
  • Zero-value nested structs: mostly presentation noise, unless the table claims a value that serialization omits. It also affects non-nil optional pointers pointing to zero-value objects—the changed Parent test is exactly that case. It isn’t limited to required objects.
  • Quoted Date: consistent with the PR’s choice to render custom-serialized values as JSON cells. A JSON date is a quoted string. Unquoted dates would be a readability enhancement; their omission from row tables remains an existing limitation.
  • Repeated marshaler reflection: reasonable cleanup, not a correctness finding. Hoisting the interface type improves clarity; I wouldn’t claim meaningful performance impact without measurement.

@ThomasRooney

Copy link
Copy Markdown
Member Author

Addressed in 2d69199b:

  • Top-level tables now honor custom JSON serialization. Flattened additional properties are retained, including the generated Metadata case.
  • Row cells and map keys/values now escape control characters, keeping tabs and newlines inside a single cell.
  • Section columns come from serialized fields. Generated Vehicle bookkeeping fields no longer introduce a TYPE column, while real API discriminator properties remain visible. Nested payloads occupy columns in the same row.
  • Custom-serialized strings, including Date, display without JSON quotes. Direct-array column selection remains unchanged.
  • Fields omitted by a root custom serializer stay omitted. Zero timestamps that the serializer actually emits remain visible; suppressing them would hide response data.
  • The JSON-marshaler reflection type is declared once and reused.

The generated Metadata, Vehicle, and Date reproductions fail against the previous renderer and pass with the fix. Committed runtime regressions also cover generated additional-properties and union models, date cells, and row/map control characters. The PR body includes the verified table example.

Validation: 651 review tests passed (2 skipped), all 1,705 primary tests passed, and product validation, output comparisons, formatting, template checks, and lint passed. The full suites used isolated mock ports after shared-port mock failures.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 11 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread zSDKs/sdk-cli/internal/output/output.go

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 7 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would auto-approve. Focused, well-tested table renderer fix restores previously omitted nested struct/map/array values in single-object and section output as compact JSON cells with wire names and custom marshalers preserved; machine formats stay unchanged and nil-section (empty) display is intentionally retained.

View guided diff | Turn on auto-fix | Re-trigger cubic

@ThomasRooney
ThomasRooney added this pull request to the merge queue Oct 7, 2026
Render complex single-object fields as JSON and append indexed details
below section tables while retaining their scalar columns.

Complex list wrappers also gain details, including pagination without a
results path. Direct arrays and declared-results aggregation retain their
existing output.
Verify compact JSON data cells for streamed union events and document the per-item pretty fallback and single-result error behavior.
Use custom JSON wire values for single-object scalar cells, including pointer-receiver values in interface arrays. Preserve timestamp and direct-list formatting.
@ThomasRooney
ThomasRooney removed this pull request from the merge queue due to a manual request Oct 7, 2026
@ThomasRooney
ThomasRooney force-pushed the fix/cli-table-nested-fields branch from 9c9dba4 to 5c7b42c Compare October 7, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants