fix(cli): expand embedded JsonNode in text-mode renderer

`profile show` puts the parsed kind:0 content under `metadata` as a
Jackson `JsonNode` (`ProfileCommands.kt:114`). The text renderer only
recursed into `Map`/`List`, so the JsonNode fell through to
`toString()` and printed as a single quoted-JSON line:

    metadata:       {"name":"Alice","picture":"…",…}

Convert any embedded JsonNode to plain Java types via
`mapper.convertValue` once at the top of `renderText`, so the same
generic walk yields:

    metadata:
      name:    Alice
      picture: …
      …

The walk handles nested cases (a hand-built Map containing a JsonNode
subtree, an ArrayNode inside a List, etc.). The `--json` shape is
untouched — Jackson's `writeValueAsString` already serialises JsonNode
natively.
This commit is contained in:
Claude
2026-04-25 13:46:55 +00:00
parent ce3a82ab3d
commit cd0b43afd9
@@ -20,6 +20,7 @@
*/
package com.vitorpamplona.amethyst.cli
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import java.time.Instant
@@ -81,24 +82,40 @@ object Output {
private fun renderText(value: Any?): String {
val color = Ansi.forStream(isStderr = false)
val out = StringBuilder()
when (value) {
when (val v = unwrap(value)) {
null -> {}
is Map<*, *> -> {
renderMapBody(out, value, "", color)
renderMapBody(out, v, "", color)
}
is List<*> -> {
renderListBody(out, value, "", color)
renderListBody(out, v, "", color)
}
else -> {
out.append(value.toString()).append('\n')
out.append(v.toString()).append('\n')
}
}
return out.toString().trimEnd('\n')
}
/**
* Convert any embedded Jackson [JsonNode] into plain Java types
* (`LinkedHashMap` / `ArrayList` / boxed primitives) so the generic
* renderer can descend into it. Plain Maps / Lists / scalars are
* returned unchanged. Walks recursively because callers commonly
* mix a JsonNode subtree into a hand-built Map (e.g. profile show
* stuffing the parsed kind:0 content under a `metadata` key).
*/
private fun unwrap(value: Any?): Any? =
when (value) {
is JsonNode -> mapper.convertValue(value, Any::class.java)
is Map<*, *> -> value.mapValues { (_, v) -> unwrap(v) }
is List<*> -> value.map { unwrap(it) }
else -> value
}
private fun renderMapBody(
out: StringBuilder,
map: Map<*, *>,