Skip to content
Open
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
86 changes: 78 additions & 8 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,29 @@ import (
"reflect"
"runtime"
"strconv"
"sync/atomic"
"unicode"
"unicode/utf16"
"unicode/utf8"
)

// UnmarshalErrHandler while decode error, will inject this handler, catch error.
// if you want to ignore this error, you can return nil
type UnmarshalErrHandler func(err error) error

var (
usedUnmarshalErrHandlerHook atomic.Bool
unmarshalErrHandlerHook UnmarshalErrHandler = func(err error) error { return err }
)

// UseUnmarshalErrHandler register a yourself error handler, only once
func UseUnmarshalErrHandler(handler UnmarshalErrHandler) {
if usedUnmarshalErrHandlerHook.Swap(true) {
return
}
unmarshalErrHandlerHook = handler
}

// Unmarshal parses the JSON-encoded data and stores the result
// in the value pointed to by v.
//
Expand Down Expand Up @@ -308,14 +326,22 @@ func (d *decodeState) init(data []byte) *decodeState {

// error aborts the decoding by panicking with err.
func (d *decodeState) error(err error) {
panic(err)
if e := unmarshalErrHandlerHook(err); e != nil {
panic(err)
} else {
d.savedError = e
}
}

// saveError saves the first err it is called with,
// for reporting at the end of the unmarshal.
func (d *decodeState) saveError(err error) {
if d.savedError == nil {
d.savedError = err
if e := unmarshalErrHandlerHook(err); e == nil {
d.savedError = e
} else {
if d.savedError == nil {
d.savedError = err
}
}
}

Expand Down Expand Up @@ -823,7 +849,7 @@ var numberType = reflect.TypeOf(Number(""))
func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted, unquotedString bool) {
// Check for unmarshaler.
if len(item) == 0 {
//Empty string given
// Empty string given
d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
return
}
Expand Down Expand Up @@ -900,17 +926,17 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted, unq
}

case '"', '\'': // string
s := item
s := string(item)
if !unquotedString {
var ok bool
s, ok = unquoteBytes(item)
ss, ok := unquoteBytes([]byte(item))
if !ok {
if fromQuoted {
d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
} else {
d.error(errPhase)
}
}
s = string(ss)
}
switch v.Kind() {
default:
Expand All @@ -921,7 +947,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted, unq
break
}
b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
n, err := base64.StdEncoding.Decode(b, s)
n, err := base64.StdEncoding.Decode(b, []byte(s))
if err != nil {
d.saveError(err)
break
Expand All @@ -935,6 +961,50 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted, unq
} else {
d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var n int64
var err error
if h, ok := hexString(s); ok {
n, err = strconv.ParseInt(h, 16, 64)
} else {
n, err = strconv.ParseInt(s, 10, 64)
}
if err != nil || v.OverflowInt(n) {
d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
break
}
v.SetInt(n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
var n uint64
var err error
if h, ok := hexString(s); ok {
n, err = strconv.ParseUint(h, 16, 64)
} else {
n, err = strconv.ParseUint(s, 10, 64)
}
if err != nil || v.OverflowUint(n) {
d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
break
}
v.SetUint(n)
case reflect.Float32, reflect.Float64:
var n float64
var err error
if h, ok := hexString(s); ok {
var nn int64
nn, err = strconv.ParseInt(h, 16, 64)
n = float64(nn)
if s[0] == '-' && nn == 0 {
n = -n
}
} else {
n, err = strconv.ParseFloat(s, v.Type().Bits())
}
if err != nil || v.OverflowFloat(n) {
d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
break
}
v.SetFloat(n)
}

default: // number
Expand Down
86 changes: 79 additions & 7 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,76 @@ var wrongStringTests = []wrongStringTest{
{`{"result":123}`, `json: invalid use of ,string struct tag, trying to unmarshal unquoted value into string`},
}

func TestErrHandler(t *testing.T) {
type ErrHandlerStruct struct {
VarName string `json:"varname,omitempty"`
Required string `json:"required,omitempty"`
Mode string `json:"mode,omitempty"`

Title string `json:"title,omitempty"`
Value string `json:"value,omitempty"`
ImageUrl string `json:"imageUrl,omitempty"`
Size int `json:"size,omitempty,string"`
}
data := `[
{
title: '我的头像',
value: '"{{ .AvatarUrl}}"',
imageUrl: 'https://www.AvatarUrlImage.com',
VarName: 666,
},
{
title: '我的昵称',
value: "{{.NickName}}",
size: '{{.Size}}',
imageUrl: 'https://www.NickNameImage.com'
}
]`
UseUnmarshalErrHandler(func(err error) error {
if err != nil {
switch {
case strings.Contains(err.Error(), "json: invalid use of ,string"):
return nil
case strings.Contains(err.Error(), "json: cannot unmarshal number"):
return nil
case strings.Contains(err.Error(), "json: cannot unmarshal string"):
return nil
case strings.Contains(err.Error(), "json: cannot unmarshal number into Go value of type string"):
return nil
}
}
return err
})

var res []*ErrHandlerStruct
dec := NewDecoder(strings.NewReader(data))
err := dec.Decode(&res)
if err == nil {
fmt.Printf("NewDecoder result: %+v\n", res)
} else {
fmt.Printf("NewDecoder result: %+v err: %v\n", res, err)
}
if len(res) != 2 {
t.Errorf("Decode: result len is not match")
}
if err != nil {
t.Errorf("Decode: error not nil")
}
var res2 []*ErrHandlerStruct
err2 := Unmarshal([]byte(data), &res2)
if err2 == nil {
fmt.Printf("Unmarshal result: %+v\n", res2)
} else {
fmt.Printf("Unmarshal result: %+v err: %v\n", res2, err2)
}
if len(res2) != 2 {
t.Errorf("Unmarshal: result len is not match")
}
if err2 != nil {
t.Errorf("Unmarshal: error not nil")
}
}

// If people misuse the ,string modifier, the error message should be
// helpful, telling the user that they're doing it wrong.
func TestErrorMessageFromMisusedString(t *testing.T) {
Expand All @@ -673,14 +743,14 @@ func TestErrorMessageFromMisusedString(t *testing.T) {
var s WrongString
err := NewDecoder(r).Decode(&s)
got := fmt.Sprintf("%v", err)
if got != tt.err {
if !usedUnmarshalErrHandlerHook.Load() && got != tt.err {
t.Errorf("%d. got err = %q, want %q", n, got, tt.err)
}
}
}

func noSpace(c rune) rune {
if isSpace(byte(c)) { //only used for ascii
if isSpace(byte(c)) { // only used for ascii
return -1
}
return c
Expand Down Expand Up @@ -1037,7 +1107,7 @@ func TestEmptyString(t *testing.T) {
dec := NewDecoder(strings.NewReader(data))
var t2 T2
err := dec.Decode(&t2)
if err == nil {
if !usedUnmarshalErrHandlerHook.Load() && err == nil {
t.Fatal("Decode: did not return error")
}
if t2.Number1 != 1 {
Expand Down Expand Up @@ -1175,7 +1245,7 @@ var decodeTypeErrorTests = []struct {
func TestUnmarshalTypeError(t *testing.T) {
for _, item := range decodeTypeErrorTests {
err := Unmarshal([]byte(item.src), item.dest)
if _, ok := err.(*UnmarshalTypeError); !ok {
if _, ok := err.(*UnmarshalTypeError); !ok && !usedUnmarshalErrHandlerHook.Load() {
t.Errorf("expected type error for Unmarshal(%q, type %T): got %T",
item.src, item.dest, err)
}
Expand Down Expand Up @@ -1337,12 +1407,14 @@ func TestInvalidUnmarshalText(t *testing.T) {
buf := []byte(`123`)
for _, tt := range invalidUnmarshalTextTests {
err := Unmarshal(buf, tt.v)
if err == nil {
if !usedUnmarshalErrHandlerHook.Load() && err == nil {
t.Errorf("Unmarshal expecting error, got nil")
continue
}
if got := err.Error(); got != tt.want {
t.Errorf("Unmarshal = %q; want %q", got, tt.want)
if !usedUnmarshalErrHandlerHook.Load() {
if got := err.Error(); got != tt.want {
t.Errorf("Unmarshal = %q; want %q", got, tt.want)
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion json5_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func TestJSON5Decode(t *testing.T) {
}
}
_, err = parseJSON5()
if err == nil {
if err == nil && !usedUnmarshalErrHandlerHook.Load() {
t.Errorf("expected JSON5 parsing to fail")
return nil
}
Expand Down