diff --git a/json/json.go b/json/json.go index 7e0515f..2b4ad38 100644 --- a/json/json.go +++ b/json/json.go @@ -161,6 +161,25 @@ func AppendEscape(b []byte, s string, flags AppendFlags) []byte { return b } +// Unescape is a convenience helper to unescape a JSON value. +// For more control over the unescape behavior and +// to write to a pre-allocated buffer, use AppendUnescape. +func Unescape(s []byte) []byte { + b := make([]byte, 0, len(s)) + return AppendUnescape(b, s, ParseFlags(0)) +} + +// AppendUnescape appends s to b with the string unescaped as a JSON value. +// This will remove starting and ending quote characters, and the +// appropriate characters will be escaped correctly as if JSON decoded. +// New space will be reallocated if more space is needed. +func AppendUnescape(b []byte, s []byte, flags ParseFlags) []byte { + d := decoder{flags: flags} + buf := new(string) + d.decodeString(s, unsafe.Pointer(buf)) + return append(b, *buf...) +} + // Compact is documented at https://golang.org/pkg/encoding/json/#Compact func Compact(dst *bytes.Buffer, src []byte) error { return json.Compact(dst, src) diff --git a/json/json_test.go b/json/json_test.go index 1811819..51f9396 100644 --- a/json/json_test.go +++ b/json/json_test.go @@ -1702,3 +1702,78 @@ func TestAppendEscape(t *testing.T) { } }) } + +func TestUnescapeString(t *testing.T) { + b := Unescape([]byte(`"value"`)) + x := []byte(`value`) + + if !bytes.Equal(x, b) { + t.Error( + "unexpected decoding:", + "expected", string(x), + "got", string(b), + ) + } +} + +func TestAppendUnescape(t *testing.T) { + t.Run("basic", func(t *testing.T) { + out := AppendUnescape([]byte{}, []byte(`"value"`), ParseFlags(0)) + exp := []byte("value") + if bytes.Compare(exp, out) != 0 { + t.Error( + "unexpected decoding:", + "expected", exp, + "got", out, + ) + } + }) + + t.Run("escaped", func(t *testing.T) { + b := AppendUnescape([]byte{}, []byte(`"\"escaped\"\t\u003cvalue\u003e"`), ParseFlags(0)) + exp := []byte(`"escaped" `) + if bytes.Compare(exp, b) != 0 { + t.Error( + "unexpected encoding:", + "expected", exp, + "got", b, + ) + } + }) + + t.Run("build", func(t *testing.T) { + b := []byte{} + b = append(b, []byte(`{"key":`)...) + b = AppendUnescape(b, []byte(`"\"escaped\"\t\u003cvalue\u003e"`), ParseFlags(0)) + b = append(b, '}') + exp := []byte(`{"key":"escaped" }`) + if bytes.Compare(exp, b) != 0 { + t.Error( + "unexpected encoding:", + "expected", string(exp), + "got", string(b), + ) + } + }) +} + +func BenchmarkUnescape(b *testing.B) { + s := []byte(`"\"escaped\"\t\u003cvalue\u003e"`) + out := []byte{} + for i := 0; i < b.N; i++ { + out = Unescape(s) + } + + b.Log(string(out)) +} + +func BenchmarkUnmarshalField(b *testing.B) { + s := []byte(`"\"escaped\"\t\u003cvalue\u003e"`) + var v string + + for i := 0; i < b.N; i++ { + json.Unmarshal(s, &v) + } + + b.Log(v) +}