feat(xce-test-cli-ux): build JSON list-output helper library

listfmt_json_begin/new_row/add_string/add_number/finish wrap cJSON
(already linked via struct2json_dep) into a minimal array/object
lifecycle library with no generic "record" abstraction -- each command's
own row struct maps its fields directly when wired in later (task 3.4).
Proven via a real stdout-capture-then-reparse round trip, including a
733-character field with no truncation, and stderr staying empty.
1306/1306 controller suite passing (+9).

Task 2.2 of .kiro/specs/xce-test-cli-ux/tasks.md.
Requirements: 5.1, 5.3

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019jaMf9QEwpugH2W9KAuc7f
This commit is contained in:
Rongsong Shen 2026-09-09 12:13:12 +00:00
parent a2686516f7
commit 07d238eada
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
6 changed files with 464 additions and 2 deletions

View file

@ -0,0 +1,59 @@
#include "listfmt_json.h"
#include <stdio.h>
cJSON* listfmt_json_begin(void)
{
return cJSON_CreateArray();
}
void listfmt_json_add_string(cJSON* row, const char* key, const char* val)
{
if (!row || !key)
return;
if (val)
cJSON_AddStringToObject(row, key, val);
else
cJSON_AddNullToObject(row, key);
}
void listfmt_json_add_number(cJSON* row, const char* key, double val)
{
if (!row || !key)
return;
cJSON_AddNumberToObject(row, key, val);
}
cJSON* listfmt_json_new_row(cJSON* array)
{
if (!array)
return NULL;
cJSON* row = cJSON_CreateObject();
if (!row)
return NULL;
if (!cJSON_AddItemToArray(array, row)) {
cJSON_Delete(row);
return NULL;
}
return row;
}
void listfmt_json_finish(cJSON* array)
{
if (!array)
return;
char* text = cJSON_Print(array);
if (text) {
fputs(text, stdout);
fputc('\n', stdout);
cJSON_free(text);
}
cJSON_Delete(array);
}