summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compat/json/borrowed_decode_test.go5
-rw-r--r--compat/json/compat.go30
-rw-r--r--compat/json/compat_test.go7
-rw-r--r--decode.go66
-rw-r--r--decode_scan.go30
-rw-r--r--errors.go4
-rw-r--r--parse.go2
7 files changed, 94 insertions, 50 deletions
diff --git a/compat/json/borrowed_decode_test.go b/compat/json/borrowed_decode_test.go
index 4b84718..4b9d0cb 100644
--- a/compat/json/borrowed_decode_test.go
+++ b/compat/json/borrowed_decode_test.go
@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"image"
- "io" // MODIFIED
"math"
"math/big"
"net"
@@ -19,8 +18,6 @@ import (
"strings"
"testing"
"time"
-
- "git.lukeshu.com/go/lowmemjson" // MODIFIED
)
type T struct {
@@ -1093,7 +1090,7 @@ func equalError(a, b error) bool {
func TestUnmarshal(t *testing.T) {
for i, tt := range unmarshalTests {
- scan := lowmemjson.ReEncoder{Out: io.Discard} // MODIFIED
+ var scan scanner
in := []byte(tt.in)
if err := checkValid(in, &scan); err != nil {
if !equalError(err, tt.err) {
diff --git a/compat/json/compat.go b/compat/json/compat.go
index 7145a86..570afc9 100644
--- a/compat/json/compat.go
+++ b/compat/json/compat.go
@@ -75,31 +75,32 @@ func HTMLEscape(dst *bytes.Buffer, src []byte) {
_, _ = formatter.Write(src)
}
+func reencode(src []byte, formatter *lowmemjson.ReEncoder) error {
+ _, err := formatter.Write(src)
+ if se, ok := err.(*lowmemjson.ReEncodeSyntaxError); ok {
+ err = &SyntaxError{
+ msg: se.Err.Error(),
+ Offset: se.Offset + 1,
+ }
+ }
+ return err
+}
+
func Compact(dst *bytes.Buffer, src []byte) error {
- formatter := &lowmemjson.ReEncoder{
+ return reencode(src, &lowmemjson.ReEncoder{
Out: dst,
Compact: true,
BackslashEscape: lowmemjson.EscapePreserve,
- }
- _, err := formatter.Write(src)
- return err
+ })
}
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
- formatter := &lowmemjson.ReEncoder{
+ return reencode(src, &lowmemjson.ReEncoder{
Out: dst,
Indent: indent,
Prefix: prefix,
BackslashEscape: lowmemjson.EscapePreserve,
- }
- _, err := formatter.Write(src)
- if se, ok := err.(*lowmemjson.ReEncodeSyntaxError); ok {
- err = &SyntaxError{
- msg: se.Err.Error(),
- Offset: se.Offset + 1,
- }
- }
- return err
+ })
}
func Valid(data []byte) bool {
@@ -153,6 +154,7 @@ func (dec *Decoder) Decode(ptr any) error {
}
} else if _, isArgErr := terr.Err.(*lowmemjson.DecodeArgumentError); terr.Err != nil &&
!isArgErr &&
+ !errors.Is(terr.Err, lowmemjson.ErrDecodeNonEmptyInterface) &&
!errors.Is(terr.Err, strconv.ErrSyntax) &&
!errors.Is(terr.Err, strconv.ErrRange) {
err = terr.Err
diff --git a/compat/json/compat_test.go b/compat/json/compat_test.go
index 5a34d22..2cb1e87 100644
--- a/compat/json/compat_test.go
+++ b/compat/json/compat_test.go
@@ -6,6 +6,7 @@ package json
import (
"bytes"
+ "io"
"git.lukeshu.com/go/lowmemjson"
"git.lukeshu.com/go/lowmemjson/internal"
@@ -13,9 +14,11 @@ import (
var parseTag = internal.ParseTag
+type scanner = lowmemjson.ReEncoder
+
func checkValid(in []byte, scan *lowmemjson.ReEncoder) error {
- _, err := scan.Write(in)
- return err
+ scan.Out = io.Discard
+ return reencode(in, scan)
}
const (
diff --git a/decode.go b/decode.go
index b1cc90f..f630541 100644
--- a/decode.go
+++ b/decode.go
@@ -91,20 +91,25 @@ func (dec *Decoder) stackStr() string {
}
func (dec *Decoder) stackParent() string {
- if len(dec.stack) > 0 && dec.stack[len(dec.stack)-1].par.Kind() == reflect.Struct {
- return dec.stack[len(dec.stack)-1].par.Name()
+ last := len(dec.stack) - 1
+ if last > 0 && dec.stack[last].par.Kind() != reflect.Struct && dec.stack[last-1].par.Kind() == reflect.Struct {
+ last--
+ }
+ if last >= 0 && dec.stack[last].par.Kind() == reflect.Struct {
+ return dec.stack[last].par.Name()
}
return ""
}
func (dec *Decoder) stackName() string {
- var fields []string
- for i := len(dec.stack) - 1; i >= 0 && dec.stack[i].par.Kind() == reflect.Struct; i-- {
- fields = append(fields, dec.stack[i].idx.(string))
+ if dec.stackParent() == "" {
+ return ""
}
- for i := 0; i < len(fields)/2; i++ {
- j := (len(fields) - 1) - i
- fields[i], fields[j] = fields[j], fields[i]
+ var fields []string
+ for _, elem := range dec.stack {
+ if elem.par.Kind() == reflect.Struct {
+ fields = append(fields, elem.idx.(string))
+ }
}
return strings.Join(fields, ".")
}
@@ -298,10 +303,10 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) {
return
}
var buf bytes.Buffer
- dec.decodeString(typ, &buf)
+ dec.decodeString(reflect.PointerTo(typ), &buf)
obj := val.Addr().Interface().(encoding.TextUnmarshaler)
if err := obj.UnmarshalText(buf.Bytes()); err != nil {
- dec.panicType("string", typ, err)
+ dec.panicType("string", reflect.PointerTo(typ), err)
}
default:
switch kind := typ.Kind(); kind {
@@ -354,7 +359,13 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) {
}
var buf strings.Builder
if typ == numberType {
- dec.scanNumber(typ, &buf)
+ t := dec.peekRuneType()
+ dec.scan(&buf)
+ if !t.IsNumber() {
+ dec.panicType(t.jsonType(), typ,
+ fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number",
+ buf.String()))
+ }
val.SetString(buf.String())
} else {
dec.decodeString(typ, &buf)
@@ -362,25 +373,32 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) {
}
case reflect.Interface:
if typ.NumMethod() > 0 {
- dec.panicType("", typ, fmt.Errorf("cannot decode in to non-empty interface"))
+ dec.panicType(dec.peekRuneType().jsonType(), typ, ErrDecodeNonEmptyInterface)
}
// If the interface stores a pointer, try to use the type information of the pointer.
if !val.IsNil() && val.Elem().Kind() == reflect.Pointer {
// Follow a chain of pointers until we find the first settable
// pointer (if any).
ptr := val.Elem()
- for ptr.Kind() == reflect.Pointer {
- if ptr.CanSet() {
- break
- }
- if ptr.IsNil() {
+ for {
+ if ptr.CanSet() || ptr.IsNil() || ptr.Elem().Kind() != reflect.Pointer {
+ // We've reached the end of the line, good or bad.
break
}
ptr = ptr.Elem()
}
- // We only neet to be able to set the pointer itself if we're
- // decoding "null", so add a "||" clause.
- if ptr.Kind() == reflect.Pointer && (ptr.CanSet() || dec.peekRuneType() != RuneTypeNullN) {
+ // ptr.Elem() != val
+ //
+ // Avoid the loop of an interface storing a pointer to its own
+ // address. We only need to worry about this at the leaf (and not
+ // in the loop) because the only way it's possible is if there's
+ // an interface in there, which'd break from the loop on its own.
+ //
+ // ptr.CanSet() || dec.peekRuneType() != RuneTypeNullN
+ //
+ // We only need the pointer itself to be settable if we're
+ // decoding null.
+ if ptr.Elem() != val && (ptr.CanSet() || dec.peekRuneType() != RuneTypeNullN) {
dec.decode(ptr, false)
break
}
@@ -406,8 +424,8 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) {
defer dec.stackPop()
idx, ok := index.byName[name]
if !ok {
- for oname, oidx := range index.byName {
- if strings.EqualFold(name, oname) {
+ for oidx := range index.byPos {
+ if strings.EqualFold(name, index.byPos[oidx].Name) {
idx = oidx
ok = true
break
@@ -426,7 +444,9 @@ func (dec *Decoder) decode(val reflect.Value, nullOK bool) {
for _, idx := range field.Path {
if fVal.Kind() == reflect.Pointer {
if fVal.IsNil() && !fVal.CanSet() { // https://golang.org/issue/21357
- dec.panicType("", fVal.Type().Elem(), fmt.Errorf("cannot set embedded pointer to unexported type"))
+ dec.panicType("", fVal.Type().Elem(),
+ fmt.Errorf("json: cannot set embedded pointer to unexported struct: %v",
+ fVal.Type().Elem()))
}
if dec.peekRuneType() != RuneTypeNullN {
if fVal.IsNil() {
diff --git a/decode_scan.go b/decode_scan.go
index 3c41df6..fee9ec6 100644
--- a/decode_scan.go
+++ b/decode_scan.go
@@ -34,20 +34,37 @@ type runeTypeScannerImpl struct {
parser Parser
offset int64
- repeat bool
- stuck bool
- rRune rune
- rSize int
- rType RuneType
- rErr error
+ repeat bool
+ stuck bool
+ rRune rune
+ rRuneOK bool
+ rSize int
+ rType RuneType
+ rErr error
}
var _ runeTypeScanner = (*runeTypeScannerImpl)(nil)
func (sc *runeTypeScannerImpl) Reset() {
sc.parser.Reset()
+ unread := sc.stuck && sc.rType == RuneTypeEOF && sc.rRuneOK
sc.stuck = false
sc.repeat = false
+ if unread {
+ // re-figure the rType and rErr
+ var err error
+ sc.rType, err = sc.parser.HandleRune(sc.rRune)
+ if err != nil {
+ sc.rErr = &DecodeSyntaxError{
+ Offset: sc.offset,
+ Err: err,
+ }
+ } else {
+ sc.rErr = nil
+ }
+ // tell it to use that rType and rErr
+ sc.UnreadRune()
+ }
}
func (sc *runeTypeScannerImpl) ReadRuneType() (rune, int, RuneType, error) {
@@ -62,6 +79,7 @@ func (sc *runeTypeScannerImpl) ReadRuneType() (rune, int, RuneType, error) {
var err error
sc.rRune, sc.rSize, err = sc.inner.ReadRune()
sc.offset += int64(sc.rSize)
+ sc.rRuneOK = err == nil
switch err {
case nil:
sc.rType, err = sc.parser.HandleRune(sc.rRune)
diff --git a/errors.go b/errors.go
index 2d8820e..e71d79a 100644
--- a/errors.go
+++ b/errors.go
@@ -76,6 +76,10 @@ func (e *DecodeTypeError) Error() string {
func (e *DecodeTypeError) Unwrap() error { return e.Err }
+var (
+ ErrDecodeNonEmptyInterface = errors.New("cannot decode in to non-empty interface")
+)
+
// high-level decode errors ////////////////////////////////////////////////////////////////////////
// A *DecodeArgumentError is returned from Decode if the argument is
diff --git a/parse.go b/parse.go
index fb72280..77d1b08 100644
--- a/parse.go
+++ b/parse.go
@@ -658,7 +658,7 @@ func (par *Parser) HandleRune(c rune) (RuneType, error) {
func (par *Parser) expectRune(c, exp rune, typ RuneType, context string, pop bool) (RuneType, error) {
if c != exp {
- return RuneTypeError, fmt.Errorf("%s: unexpected character: %q", context, c)
+ return RuneTypeError, fmt.Errorf("invalid character %q in literal %s (expecting %q)", c, context, exp)
}
if pop {
par.popState()