From 95e5a13116d0d8cf6af64ee5940c19797e48e6cf Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 24 Feb 2023 23:38:15 -0700 Subject: decode: Add a DecodeArray function --- ReleaseNotes.md | 2 ++ decode.go | 81 ++++++++++++++++++++++++++++++++++++++++++++------------- decode_test.go | 22 ++++++++++++++-- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 20bcd65..48982e4 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -4,6 +4,8 @@ User-facing changes: + - Feature: There is a new `DecodeString` helper function. + - Change: Decoder: No longer bails when a type error (`DecodeTypeError`) is encountered. The part of the output value with the type error is either unmodified (if already existing) or diff --git a/decode.go b/decode.go index c987d79..d4ecd4f 100644 --- a/decode.go +++ b/decode.go @@ -1011,6 +1011,7 @@ func DecodeObject(r io.RuneScanner, decodeKey, decodeVal func(io.RuneScanner) er } dec.posStackPush() defer dec.posStackPop() + // TODO Find a better Go type to use than `nil`. if err := dec.decodeObject(nil, func() *DecodeError { dec.posStackPush() @@ -1101,6 +1102,7 @@ func DecodeArray(r io.RuneScanner, decodeMember func(r io.RuneScanner) error) er } dec.posStackPush() defer dec.posStackPop() + // TODO Find a better Go type to use than `nil`. if err := dec.decodeArray(nil, func() *DecodeError { dec.posStackPush() defer dec.posStackPop() @@ -1151,6 +1153,38 @@ func (dec *Decoder) decodeArray(gTyp reflect.Type, decodeMember func() *DecodeEr } } +// DecodeString is a helper function to eas implementing the Decodable +// interface; allowing the lowmemjson package to handle decoding +// character escapes and such, while the Decodable only needs to +// handle what to do with the decoded runes. +// +// Outside of implementing Decodable.DecodeJSON methods, callers +// should instead simply use NewDecoder(r).Decode(&val) rather than +// attempting to call DecodeString directly. +func DecodeString(in io.RuneScanner, out fastio.RuneWriter) error { + var dec *Decoder + if dr, ok := in.(*decRuneScanner); ok { + dec = dr.dec + } else { + dec = NewDecoder(in) + } + if dec.typeErr != nil { + oldTypeErr := dec.typeErr + dec.typeErr = nil + defer func() { dec.typeErr = oldTypeErr }() + } + dec.posStackPush() + defer dec.posStackPop() + // TODO Find a better Go type to use than `nil`. + if err := dec.decodeString(nil, out); err != nil { + return err + } + if dec.typeErr != nil { + return dec.typeErr + } + return nil +} + func (dec *Decoder) decodeString(gTyp reflect.Type, out fastio.RuneWriter) *DecodeError { if _, t, err := dec.readRune(); err != nil { return err @@ -1167,29 +1201,30 @@ func (dec *Decoder) decodeString(gTyp reflect.Type, out fastio.RuneWriter) *Deco } switch t { case jsonparse.RuneTypeStringChar: - _, _ = out.WriteRune(c) + if _, err := out.WriteRune(c); err != nil { + dec.newTypeError("string", gTyp, err) + } case jsonparse.RuneTypeStringEsc, jsonparse.RuneTypeStringEscU: // do nothing case jsonparse.RuneTypeStringEsc1: switch c { - case '"': - _, _ = out.WriteRune('"') - case '\\': - _, _ = out.WriteRune('\\') - case '/': - _, _ = out.WriteRune('/') + case '"', '\\', '/': + // self case 'b': - _, _ = out.WriteRune('\b') + c = '\b' case 'f': - _, _ = out.WriteRune('\f') + c = '\f' case 'n': - _, _ = out.WriteRune('\n') + c = '\n' case 'r': - _, _ = out.WriteRune('\r') + c = '\r' case 't': - _, _ = out.WriteRune('\t') + c = '\t' default: - panic(fmt.Errorf("should not happen: unexpected rune after backslash: %q", c)) + panic(fmt.Errorf("should not happen: rune %q is not a RuneTypeStringEsc1", c)) + } + if _, err := out.WriteRune(c); err != nil { + dec.newTypeError("string", gTyp, err) } case jsonparse.RuneTypeStringEscUA: uhex[0] = byte(c) @@ -1206,7 +1241,9 @@ func (dec *Decoder) decodeString(gTyp reflect.Type, out fastio.RuneWriter) *Deco return err } if t != jsonparse.RuneTypeStringEsc { - _, _ = out.WriteRune(utf8.RuneError) + if _, err := out.WriteRune(utf8.RuneError); err != nil { + dec.newTypeError("string", gTyp, err) + } break } if err := dec.expectRuneOrPanic('\\', jsonparse.RuneTypeStringEsc); err != nil { @@ -1217,7 +1254,9 @@ func (dec *Decoder) decodeString(gTyp reflect.Type, out fastio.RuneWriter) *Deco return err } if t != jsonparse.RuneTypeStringEscU { - _, _ = out.WriteRune(utf8.RuneError) + if _, err := out.WriteRune(utf8.RuneError); err != nil { + dec.newTypeError("string", gTyp, err) + } break } if err := dec.expectRuneOrPanic('u', jsonparse.RuneTypeStringEscU); err != nil { @@ -1246,13 +1285,19 @@ func (dec *Decoder) decodeString(gTyp reflect.Type, out fastio.RuneWriter) *Deco c2 := hexToRune(uhex[0], uhex[1], uhex[2], byte(b)) d := utf16.DecodeRune(c, c2) if d == utf8.RuneError { - _, _ = out.WriteRune(utf8.RuneError) + if _, err := out.WriteRune(utf8.RuneError); err != nil { + dec.newTypeError("string", gTyp, err) + } c = c2 goto handleUnicode } - _, _ = out.WriteRune(d) + if _, err := out.WriteRune(d); err != nil { + dec.newTypeError("string", gTyp, err) + } } else { - _, _ = out.WriteRune(c) + if _, err := out.WriteRune(c); err != nil { + dec.newTypeError("string", gTyp, err) + } } case jsonparse.RuneTypeStringEnd: return nil diff --git a/decode_test.go b/decode_test.go index c224f3a..6d27347 100644 --- a/decode_test.go +++ b/decode_test.go @@ -82,16 +82,34 @@ func (o *testObj) DecodeJSON(r io.RuneScanner) error { ) } +type testStr string + +func (s *testStr) DecodeJSON(r io.RuneScanner) error { + var buf strings.Builder + if err := DecodeString(r, &buf); err != nil { + return err + } + *s = testStr(buf.String()) + return nil +} + +var ( + _ Decodable = (*testAry)(nil) + _ Decodable = (*testObj)(nil) + _ Decodable = (*testStr)(nil) +) + func TestDecodeTypeError(t *testing.T) { t.Parallel() type outType struct { First int Second testAry Third testObj + Fourth testStr } var out outType - err := NewDecoder(strings.NewReader(`{"First": 1.2, "Second": [3], "Third": {"a":4}}`)).Decode(&out) + err := NewDecoder(strings.NewReader(`{"First": 1.2, "Second": [3], "Third": {"a":4}, "Fourth": "foo\u000Abar"}`)).Decode(&out) assert.EqualError(t, err, `json: v["First"]: cannot decode JSON number 1.2 at input byte 9 into Go int: strconv.ParseInt: parsing "1.2": invalid syntax`) - assert.Equal(t, outType{First: 0, Second: testAry{3}, Third: testObj{"a": 4}}, out) + assert.Equal(t, outType{First: 0, Second: testAry{3}, Third: testObj{"a": 4}, Fourth: "foo\nbar"}, out) } -- cgit v1.2.3