@@ -255,21 +255,74 @@ func flattenValue(out map[string]any, val reflect.Value, prefix string) {
255255 flattenValue (out , val .Field (i ), key )
256256 }
257257 case reflect .Map :
258- for _ , mapKey := range val .MapKeys () {
259- key := joinKey (prefix , fmt .Sprintf ("%v" , mapKey .Interface ()))
260- flattenValue (out , val .MapIndex (mapKey ), key )
258+ // Render maps as a single-line compact-JSON value rather than
259+ // exploding every key into its own row. Deeply nested values.yaml
260+ // fragments would otherwise flood the table, and a raw %v dump of a
261+ // nested map breaks the FIELD/VALUE columns (issue #1383). An empty
262+ // top-level map yields no rows so the caller prints "<empty>".
263+ if prefix == "" && val .Len () == 0 {
264+ return
261265 }
266+ storeTableLeaf (out , prefix , compactJSONLeaf (val ))
262267 case reflect .Slice , reflect .Array :
263- for i := 0 ; i < val .Len (); i ++ {
264- key := joinKey (prefix , fmt .Sprintf ("[%d]" , i ))
265- flattenValue (out , val .Index (i ), key )
268+ // Slices of structs (e.g. []ComponentRef) still recurse so each
269+ // element's scalar fields get their own row; slices of scalars
270+ // (e.g. deploymentOrder) collapse to one compact-JSON value.
271+ if val .Len () > 0 && derefedKind (val .Index (0 )) == reflect .Struct {
272+ for i := 0 ; i < val .Len (); i ++ {
273+ key := joinKey (prefix , fmt .Sprintf ("[%d]" , i ))
274+ flattenValue (out , val .Index (i ), key )
275+ }
276+ } else {
277+ // An empty top-level slice yields no rows so the caller prints
278+ // "<empty>"; a nested empty slice still renders as "[]".
279+ if prefix == "" && val .Len () == 0 {
280+ return
281+ }
282+ storeTableLeaf (out , prefix , compactJSONLeaf (val ))
266283 }
267284 default :
268- if prefix == "" {
269- prefix = defaultValueKey
285+ // Scalars render natively; a string with an embedded newline, tab, or
286+ // carriage return is JSON-escaped so it can't shatter the tabwriter
287+ // FIELD/VALUE columns.
288+ if s , ok := val .Interface ().(string ); ok && strings .ContainsAny (s , "\n \r \t " ) {
289+ storeTableLeaf (out , prefix , compactJSONLeaf (val ))
290+ } else {
291+ storeTableLeaf (out , prefix , val .Interface ())
292+ }
293+ }
294+ }
295+
296+ // storeTableLeaf records a flattened leaf, substituting defaultValueKey when
297+ // the value sits at the root (no prefix).
298+ func storeTableLeaf (out map [string ]any , prefix string , v any ) {
299+ if prefix == "" {
300+ prefix = defaultValueKey
301+ }
302+ out [prefix ] = v
303+ }
304+
305+ // compactJSONLeaf renders a non-scalar (or multi-line) value as single-line
306+ // JSON so the table's FIELD/VALUE columns stay intact. Falls back to %v when
307+ // the value cannot be marshaled (e.g. a map with non-string keys).
308+ func compactJSONLeaf (val reflect.Value ) string {
309+ b , err := json .Marshal (val .Interface ())
310+ if err != nil {
311+ return fmt .Sprintf ("%v" , val .Interface ())
312+ }
313+ return string (b )
314+ }
315+
316+ // derefedKind returns the underlying kind of v after unwrapping pointers and
317+ // interfaces; reflect.Invalid for a nil along the way.
318+ func derefedKind (v reflect.Value ) reflect.Kind {
319+ for v .Kind () == reflect .Pointer || v .Kind () == reflect .Interface {
320+ if v .IsNil () {
321+ return reflect .Invalid
270322 }
271- out [ prefix ] = val . Interface ()
323+ v = v . Elem ()
272324 }
325+ return v .Kind ()
273326}
274327
275328func joinKey (prefix , suffix string ) string {
0 commit comments