From 691af6ca28dad9c72e51346fe10c6aaadc3f940b Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Thu, 31 Jul 2025 16:21:41 -0700 Subject: [PATCH] encoding/json: fix Indent trailing whitespace regression in goexperiment.jsonv2 The Indent function preserves trailing whitespace, while the v1 emulation under v2 implementation accidentally dropped it. There was prior logic that attempted to preserve it, but it did not work correctly since it ran in a defer and accidentally mutated the dst input argument rather than the output argument. Move the logic to the end and avoid a defer. Also, add a test to both v1 and v1in2 to codify this behavior. This only modifies code that is compiled in under goexperiment.jsonv2. Updates #13520 Fixes #74806 Change-Id: I22b1a8da5185eb969e2a8a111b625d3752cfcbe8 Reviewed-on: https://go-review.googlesource.com/c/go/+/692195 Reviewed-by: Sean Liao LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov Reviewed-by: David Chase Auto-Submit: Sean Liao --- src/encoding/json/scanner_test.go | 3 ++- src/encoding/json/v2_indent.go | 16 ++++++---------- src/encoding/json/v2_scanner_test.go | 3 ++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/encoding/json/scanner_test.go b/src/encoding/json/scanner_test.go index fb64463599..a062e91243 100644 --- a/src/encoding/json/scanner_test.go +++ b/src/encoding/json/scanner_test.go @@ -74,6 +74,7 @@ func TestCompactAndIndent(t *testing.T) { -5e+2 ]`}, {Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070 + {Name(""), `null`, "null \n\r\t"}, // See golang.org/issue/13520 and golang.org/issue/74806 } var buf bytes.Buffer for _, tt := range tests { @@ -102,7 +103,7 @@ func TestCompactAndIndent(t *testing.T) { buf.Reset() if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { t.Errorf("%s: Indent error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.indent { + } else if got := buf.String(); got != strings.TrimRight(tt.indent, " \n\r\t") { t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent)) } }) diff --git a/src/encoding/json/v2_indent.go b/src/encoding/json/v2_indent.go index 2655942b12..b2e8518471 100644 --- a/src/encoding/json/v2_indent.go +++ b/src/encoding/json/v2_indent.go @@ -88,17 +88,8 @@ func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { } func appendIndent(dst, src []byte, prefix, indent string) ([]byte, error) { - // In v2, trailing whitespace is discarded, while v1 preserved it. - dstLen := len(dst) - if n := len(src) - len(bytes.TrimRight(src, " \n\r\t")); n > 0 { - // Append the trailing whitespace afterwards. - defer func() { - if len(dst) > dstLen { - dst = append(dst, src[len(src)-n:]...) - } - }() - } // In v2, only spaces and tabs are allowed, while v1 allowed any character. + dstLen := len(dst) if len(strings.Trim(prefix, " \t"))+len(strings.Trim(indent, " \t")) > 0 { // Use placeholder spaces of correct length, and replace afterwards. invalidPrefix, invalidIndent := prefix, indent @@ -129,5 +120,10 @@ func appendIndent(dst, src []byte, prefix, indent string) ([]byte, error) { if err != nil { return dst[:dstLen], transformSyntacticError(err) } + + // In v2, trailing whitespace is discarded, while v1 preserved it. + if n := len(src) - len(bytes.TrimRight(src, " \n\r\t")); n > 0 { + dst = append(dst, src[len(src)-n:]...) + } return dst, nil } diff --git a/src/encoding/json/v2_scanner_test.go b/src/encoding/json/v2_scanner_test.go index bec5521274..8885520e6d 100644 --- a/src/encoding/json/v2_scanner_test.go +++ b/src/encoding/json/v2_scanner_test.go @@ -74,6 +74,7 @@ func TestCompactAndIndent(t *testing.T) { -5e+2 ]`}, {Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070 + {Name(""), `null`, "null \n\r\t"}, // See golang.org/issue/13520 and golang.org/issue/74806 } var buf bytes.Buffer for _, tt := range tests { @@ -102,7 +103,7 @@ func TestCompactAndIndent(t *testing.T) { buf.Reset() if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { t.Errorf("%s: Indent error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.indent { + } else if got := buf.String(); got != strings.TrimRight(tt.indent, " \n\r\t") { t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent)) } }) -- 2.51.0