From 228ca6d0814ed8fac523df332180d63940aee37d Mon Sep 17 00:00:00 2001 From: Buck Evan Date: Fri, 14 Nov 2025 16:11:13 -0600 Subject: [PATCH] --json: self-closing tags have null content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empty XML elements (self-closing tags, immediate-closing tags, and whitespace-only elements) now output as null in JSON instead of empty objects {}. This makes the JSON formatter consistent with the XML formatter, which already treats these elements as semantically empty by collapsing them to self-closing tags. Changes: - nodeToJSONInternal() returns nil for empty elements - Added TestSelfClosingTagsHaveNullContent test - Added TestWhitespaceOnlyTagsAreSelfClosing test Example: → {"tag": null} (was {"tag": {}}) → {"tag": null} (was {"tag": {}}) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/utils/jsonutil.go | 4 ++++ internal/utils/jsonutil_test.go | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/internal/utils/jsonutil.go b/internal/utils/jsonutil.go index 50b16ca..dc2cfb8 100644 --- a/internal/utils/jsonutil.go +++ b/internal/utils/jsonutil.go @@ -84,9 +84,13 @@ func nodeToJSONInternal(node *xmlquery.Node, depth int) interface{} { if len(textParts) > 0 { if len(result) == 0 { + // Element contains only text return strings.Join(textParts, "\n") } result["#text"] = strings.Join(textParts, "\n") + } else if len(result) == 0 { + // Self-closing tags have null content + return nil } return result diff --git a/internal/utils/jsonutil_test.go b/internal/utils/jsonutil_test.go index 22591a4..71551a1 100644 --- a/internal/utils/jsonutil_test.go +++ b/internal/utils/jsonutil_test.go @@ -45,3 +45,45 @@ func TestXmlToJSON(t *testing.T) { assert.Equal(t, expectedJson, output.String()) } } + +func TestSelfClosingTagsHaveNullContent(t *testing.T) { + // Self-closing XML elements should have null content in JSON + xmlInput := `` + + node, err := xmlquery.Parse(strings.NewReader(xmlInput)) + assert.NoError(t, err) + + result := NodeToJSON(node, -1) + assert.NotNil(t, result) + + resultMap, ok := result.(map[string]interface{}) + assert.True(t, ok) + + rootMap, ok := resultMap["root"].(map[string]interface{}) + assert.True(t, ok) + + assert.Nil(t, rootMap["self-closing"], "Self-closing tag should have null content") +} + +func TestWhitespaceOnlyTagsAreSelfClosing(t *testing.T) { + // Establishes that the XML formatter treats whitespace-only elements as self-closing + xmlInput := ` +` + + node, err := xmlquery.Parse(strings.NewReader(xmlInput)) + assert.NoError(t, err) + + result := NodeToJSON(node, -1) + assert.NotNil(t, result) + + resultMap, ok := result.(map[string]interface{}) + assert.True(t, ok) + + rootMap, ok := resultMap["root"].(map[string]interface{}) + assert.True(t, ok) + + assert.Nil(t, rootMap["spaces"], "Spaces should have null content") + assert.Nil(t, rootMap["newlines"], "Newlines should have null content") + assert.Nil(t, rootMap["empty"], "Empty should have null content") + assert.Nil(t, rootMap["immediate-closing"], "Immediate-closing should have null content") +}