Skip to content

Expose Unescape + AppendUnescape helper functions #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super familiar with this code, so bear with me here. 😁 Is it possible to decode straight into b to avoid allocating to buf and then copying?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unlike encodeString(), decodeString() does not append the result string to the buffer passed it, rather it simply overwrites the string at the pointer passed in:
*(*string)(p) = string(s), where p is the passed in buffer. I'd like to avoid modifying the underlying function for this simple change, but perhaps there are options I am be missing!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe using decodeString isn't the right approach, have you looked into parseStringUnquote ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you'd have to reimplement the handling of parsing flag, maybe that's not the right path.

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)
Expand Down
75 changes: 75 additions & 0 deletions json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" <value>`)
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" <value>}`)
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)
}