Skip to content
This repository was archived by the owner on Mar 31, 2023. It is now read-only.

Commit

Permalink
Fix JSONFormat to output valid JSON strings
Browse files Browse the repository at this point in the history
  • Loading branch information
itchyny committed Apr 12, 2022
1 parent a8056c2 commit 416cf74
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 12 deletions.
65 changes: 53 additions & 12 deletions json.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"math"
"reflect"
"strconv"
"strings"
"time"
"unicode/utf8"
)
Expand Down Expand Up @@ -190,15 +189,11 @@ func appendJSON(buf []byte, v interface{}) ([]byte, error) {
}
return appendFloat(buf, t, 64), nil
case string:
if !utf8.ValidString(t) {
// the next line replaces invalid characters.
t = strings.ToValidUTF8(t, string(utf8.RuneError))
}
// escaped length = 2*len(t) + 2 double quotes
if cap(buf) < (len(t)*2 + 2) {
return nil, ErrTooLarge
}
return strconv.AppendQuote(buf, t), nil
return appendString(buf, t), nil
case json.Marshaler:
s, err := t.MarshalJSON()
if err != nil {
Expand All @@ -223,18 +218,14 @@ func appendJSON(buf []byte, v interface{}) ([]byte, error) {
if cap(buf) < (len(s)*2 + 2) {
return nil, ErrTooLarge
}
return strconv.AppendQuote(buf, string(s)), nil
return appendString(buf, string(s)), nil
case error:
s := t.Error()
if !utf8.ValidString(s) {
// the next line replaces invalid characters.
s = strings.ToValidUTF8(s, string(utf8.RuneError))
}
// escaped length = 2*len(s) + 2 double quotes
if cap(buf) < (len(s)*2 + 2) {
return nil, ErrTooLarge
}
return strconv.AppendQuote(buf, s), nil
return appendString(buf, s), nil
}

value := reflect.ValueOf(v)
Expand Down Expand Up @@ -316,3 +307,53 @@ func appendFloat(buf []byte, v float64, bitSize int) []byte {
return strconv.AppendFloat(buf, v, 'f', -1, bitSize)
}
}

// Ref: encodeState#string in encoding/json.
func appendString(buf []byte, s string) []byte {
buf = append(buf, '"')
start := 0
for i := 0; i < len(s); {
if b := s[i]; b < utf8.RuneSelf {
if ' ' <= b && b <= '~' && b != '"' && b != '\\' {
i++
continue
}
if start < i {
buf = append(buf, s[start:i]...)
}
switch b {
case '"':
buf = append(buf, `\"`...)
case '\\':
buf = append(buf, `\\`...)
case '\n':
buf = append(buf, `\n`...)
case '\r':
buf = append(buf, `\r`...)
case '\t':
buf = append(buf, `\t`...)
default:
const hex = "0123456789abcdef"
buf = append(buf, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
}
i++
start = i
continue
}
c, size := utf8.DecodeRuneInString(s[i:])
if c == utf8.RuneError && size == 1 {
if start < i {
buf = append(buf, s[start:i]...)
}
buf = append(buf, `\ufffd`...)
i += size
start = i
continue
}
i += size
}
if start < len(s) {
buf = append(buf, s[start:]...)
}
return append(buf, '"')
}
9 changes: 9 additions & 0 deletions json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func TestJSONFormat(t *testing.T) {
"float32": []float32{3.14159, float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1))},
"float64": []float64{3.14159, math.NaN(), math.Inf(1), math.Inf(-1)},
"invalid": "12\xc534",
"escapes": "abc\x00\a\b\f\n\r\t\v\\'\"\x7f\U0001fbffdef",
"tm": testTextMarshal{},
"jm": testJSONMarshal{},
"err": testError{},
Expand Down Expand Up @@ -191,6 +192,14 @@ func TestJSONFormat(t *testing.T) {
}
}

if v, ok := j["escapes"]; !ok {
t.Error(`v, ok := j["escapes"]; !ok`)
} else {
if got, want := v.(string), "abc\x00\a\b\f\n\r\t\v\\'\"\x7f\U0001fbffdef"; got != want {
t.Errorf("got %q, want %q", got, want)
}
}

if v, ok := j["tm"]; !ok {
t.Error(`v, ok := j["tm"]; !ok`)
} else {
Expand Down

0 comments on commit 416cf74

Please sign in to comment.