From cd0b43afd9b1cb0fe786ec6d0f797e8e50bd9fc6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Apr 2026 13:46:55 +0000 Subject: [PATCH] fix(cli): expand embedded JsonNode in text-mode renderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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. --- .../com/vitorpamplona/amethyst/cli/Output.kt | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Output.kt b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Output.kt index 3456b67e08..634ca29e14 100644 --- a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Output.kt +++ b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Output.kt @@ -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<*, *>,