fix(cli): format arrays of empty objects without keys as JSON (#7058)

When formatting arrays of objects that have no own enumerable keys as
Markdown, formatObjectTable derives empty headers and outputs a malformed
table with an invalid delimiter row. Require at least one item with keys in
isObjectArray and fall back to JSON serialization when headers are empty.

Fixes #7057
This commit is contained in:
Vinícius Andrade authored and GitHub committed 2026-10-25 10:46:15 +08:00
1 parent 75e0d620bf
commit 4ed33efb0c
3 files changed
+21 -1

No files matched your search

+4 -1
View File
@@ -40,7 +40,8 @@ const isObjectArray = (value: unknown): value is Record<string, unknown>[] =>
value.length > 0 &&
value.every(
(item) => typeof item === "object" && item !== null && !Array.isArray(item),
);
) &&
value.some((item) => Object.keys(item).length > 0);
const escapeTableCell = (value: string): string =>
value
@@ -55,6 +56,7 @@ const renderTable = (
headers: readonly string[],
rows: readonly string[][],
): string => {
if (headers.length === 0) return "";
const escapedHeaders = headers.map(escapeTableCell);
const widths = escapedHeaders.map((header) => header.length);
for (const row of rows) {
@@ -105,6 +107,7 @@ const formatObjectTable = (
seen.add(key);
headers.push(key);
}
if (headers.length === 0) return markdownCell(items);
return renderTable(
headers,
items.map((item) =>
+8
View File
@@ -34,6 +34,14 @@ describe("CLI output argument boundary", () => {
expect(JSON.parse(renderCliDocument(value, "json", false))).toEqual(value);
});
it("formats arrays of empty objects without column keys as JSON in Markdown", () => {
expect(renderCliDocument([{}], "md", false)).toBe("[{}]\n");
expect(renderCliDocument([{}, {}], "md", false)).toBe("[{},{}]\n");
expect(renderCliDocument({ empty: [{}] }, "md", false)).toBe(
"## empty\n\n[{}]\n",
);
});
it("rejects token windows that would corrupt structured output", () => {
for (const format of ["json", "jsonl", "yaml"] as const) {
const validation = parseCliOutputArguments([
@@ -91,6 +91,15 @@ it("renders only each record's own fields in heterogeneous Markdown tables", asy
]);
});
it("formats arrays of empty objects without columns as JSON in Markdown", async () => {
const value: unknown = [{}, {}];
freezeRecursively(value);
const json = await runFilteredCli(value, "json");
expect(JSON.parse(json)).toEqual(value);
const markdown = await runFilteredCli(value, "md");
expect(markdown).toBe("[{},{}]\n");
});
it.each(
(["json", "jsonl"] as const).flatMap((format) =>
[false, true].map((full) => ({ format, full })),