#5913·beego

`JSON(..., encoding=true)` corrupts supplementary Unicode characters

Author: NaimurRahmannnCreated Sep 4, 2026Updated Sep 4, 2026

Summary

When the encoding argument of BeegoOutput.JSON is set to true, supplementary Unicode characters with code points above U+FFFF are encoded incorrectly.

For example, the emoji (U+1F600) is emitted as:

json
{"emoji":"\u1f600"}

However, JSON \\\u escape sequences contain exactly four hexadecimal digits. As a result, a JSON decoder interprets \u1f600 as \u1f60 followed by the literal character 0, producing corrupted data.

The decoded value becomes:

"ὠ0"

instead of:

""

Because the generated JSON can still be parsed successfully, this may result in silent Unicode data corruption rather than an obvious decoding error.

This appears to be related to the older #2638 fix, which padded Unicode code points shorter than four hexadecimal digits, but did not handle supplementary characters above U+FFFF, which require UTF-16 surrogate pairs.

Affected Versions

  • Beego v2.3.10
  • master at commit 939cfde380bb9f15844ad633b84f037f7da21584

Steps to Reproduce

go
package main

import (
	"encoding/json"
	"fmt"
	"net/http/httptest"

	beecontext "github.com/beego/beego/v2/server/web/context"
)

func main() {
	req := httptest.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()

	ctx := beecontext.NewContext()
	ctx.Reset(&beecontext.Response{ResponseWriter: rec}, req)

	if err := ctx.Output.JSON(
		map[string]string{"emoji": ""},
		false,
		true,
	); err != nil {
		panic(err)
	}

	var decoded map[string]string
	if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
		panic(err)
	}

	fmt.Printf("wire: %s\n", rec.Body.String())
	fmt.Printf("decoded: %q\n", decoded["emoji"])
}

Actual Behavior

The generated JSON is:

wire: {"emoji":"\u1f600"}

After decoding:

decoded: "ὠ0"

The original Unicode character is corrupted.

Expected Behavior

The decoded value should remain:

""

If non-ASCII characters are escaped, supplementary characters should be represented using a valid UTF-16 surrogate pair.

For example:

json
{"emoji":"\ud83d\ude00"}

Root Cause

The issue appears to originate from the Unicode escaping logic in stringsToJSON.

The implementation writes each Unicode code point after a single \\\u prefix:

go
jsons.WriteString("\\\\u")
jsons.WriteString(strconv.FormatInt(int64(rint), 16))

This approach works for characters in the Basic Multilingual Plane when they can be represented using a single \\\uXXXX escape.

For code points above U+FFFF, JSON requires a UTF-16 surrogate pair.

For example:

U+1F600

should be represented as:

\uD83D\uDE00

rather than:

\u1f600

Possible Fix

One possible fix would be to detect code points above U+FFFF, use utf16.EncodeRune, and emit both surrogate halves as four-digit \\\uXXXX escapes.

Alternatively, the custom Unicode escaping logic could be replaced with a standard-library-based implementation that guarantees valid JSON escaping.

Regression tests could also be added for cases such as:

  • U+FFFF
  • U+10000
  • emoji such as
  • mixed BMP and non-BMP Unicode strings
  • JSON encode/decode round-trip preservation

I would be happy to submit a PR if this approach is considered appropriate.

Environment

  • Beego: v2.3.10
  • Master commit: 939cfde380bb9f15844ad633b84f037f7da21584
  • Go: 1.26.3
  • OS: Windows
  • Architecture: amd64

The issue appears to be platform-independent.