summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2023-02-24 23:38:15 -0700
committerLuke Shumaker <lukeshu@lukeshu.com>2023-02-25 00:57:52 -0700
commit95e5a13116d0d8cf6af64ee5940c19797e48e6cf (patch)
tree31f9fd24375b1db02d47b7c80dcb02544c2a1582
parente3c62b8de808c29ad70c1cfb98b4d1b164c05745 (diff)
decode: Add a DecodeArray function
-rw-r--r--ReleaseNotes.md2
-rw-r--r--decode.go81
-rw-r--r--decode_test.go22
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)
}