Skip to content

added fuzzy decoding for booleans, added ability to decode numeric keyed objects as arrays with fizzy decoder #500

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions extra/fuzzy_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package extra

import (
"encoding/json"
"fmt"
"io"
"math"
"reflect"
"strconv"
"strings"
"unsafe"

Expand All @@ -20,7 +22,9 @@ const minInt = -maxInt - 1
// It will handle string/number auto conversation, and treat empty [] as empty struct.
func RegisterFuzzyDecoders() {
jsoniter.RegisterExtension(&tolerateEmptyArrayExtension{})
jsoniter.RegisterExtension(&numericKeyedObjectToArrayExtension{})
jsoniter.RegisterTypeDecoder("string", &fuzzyStringDecoder{})
jsoniter.RegisterTypeDecoder("bool", &fuzzyBoolDecoder{})
jsoniter.RegisterTypeDecoder("float32", &fuzzyFloat32Decoder{})
jsoniter.RegisterTypeDecoder("float64", &fuzzyFloat64Decoder{})
jsoniter.RegisterTypeDecoder("int", &fuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
Expand Down Expand Up @@ -171,6 +175,114 @@ func (decoder *tolerateEmptyArrayDecoder) Decode(ptr unsafe.Pointer, iter *jsoni
}
}

type numericKeyedObjectToArrayExtension struct {
jsoniter.DummyExtension
}

func (extension *numericKeyedObjectToArrayExtension) DecorateDecoder(typ reflect2.Type, decoder jsoniter.ValDecoder) jsoniter.ValDecoder {
if typ.Kind() == reflect.Slice {
sliceType := typ.(*reflect2.UnsafeSliceType)
return &numericKeyedObjectToSliceDecoder{valDecoder: decoder, sliceType: sliceType, elemType: sliceType.Elem()}
} else if typ.Kind() == reflect.Array {
arrayType := typ.(*reflect2.UnsafeArrayType)
return &numericKeyedObjectToArrayDecoder{valDecoder: decoder, arrayType: arrayType, elemType: arrayType.Elem()}
}

return decoder
}

type numericKeyedObjectToSliceDecoder struct {
valDecoder jsoniter.ValDecoder
sliceType *reflect2.UnsafeSliceType
elemType reflect2.Type
}

func (decoder *numericKeyedObjectToSliceDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
if iter.WhatIsNext() != jsoniter.ObjectValue {
decoder.valDecoder.Decode(ptr, iter)
return
}

decoder.doDecode(ptr, iter)
if iter.Error != nil && iter.Error != io.EOF {
iter.Error = fmt.Errorf("%v: %s", decoder.sliceType, iter.Error.Error())
}
}

func (decoder *numericKeyedObjectToSliceDecoder) doDecode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
length := 0
lastIndex := -1
iter.ReadMapCB(func(iter *jsoniter.Iterator, field string) bool {
index, err := strconv.Atoi(field)
if err != nil {
iter.Error = fmt.Errorf("%v: %s", decoder.sliceType, iter.Error.Error())
return false
}
if index <= lastIndex {
iter.Error = fmt.Errorf("%v: %s", decoder.sliceType, "map keys must be strictly increasing")
return false
}
lastIndex = index

idx := length
length += 1
decoder.sliceType.UnsafeGrow(ptr, length)
elemPtr := decoder.elemType.New()
iter.ReadVal(elemPtr)
decoder.sliceType.UnsafeSetIndex(ptr, idx, reflect2.PtrOf(elemPtr))

return true
})
}

type numericKeyedObjectToArrayDecoder struct {
valDecoder jsoniter.ValDecoder
arrayType *reflect2.UnsafeArrayType
elemType reflect2.Type
}

func (decoder *numericKeyedObjectToArrayDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
if iter.WhatIsNext() != jsoniter.ObjectValue {
decoder.valDecoder.Decode(ptr, iter)
return
}

decoder.doDecode(ptr, iter)
if iter.Error != nil && iter.Error != io.EOF {
iter.Error = fmt.Errorf("%v: %s", decoder.arrayType, iter.Error.Error())
}
}

func (decoder *numericKeyedObjectToArrayDecoder) doDecode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
length := 0
lastIndex := -1
iter.ReadMapCB(func(iter *jsoniter.Iterator, field string) bool {
index, err := strconv.Atoi(field)
if err != nil {
iter.Error = fmt.Errorf("%v: %s", decoder.arrayType, iter.Error.Error())
return false
}
if index <= lastIndex {
iter.Error = fmt.Errorf("%v: %s", decoder.arrayType, "map keys must be strictly increasing")
return false
}
lastIndex = index

if length >= decoder.arrayType.Len() {
iter.Skip()
return true
}

idx := length
length += 1
elemPtr := decoder.elemType.New()
iter.ReadVal(elemPtr)
decoder.arrayType.UnsafeSetIndex(ptr, idx, reflect2.PtrOf(elemPtr))

return true
})
}

type fuzzyStringDecoder struct {
}

Expand Down Expand Up @@ -292,3 +404,35 @@ func (decoder *fuzzyFloat64Decoder) Decode(ptr unsafe.Pointer, iter *jsoniter.It
iter.ReportError("fuzzyFloat64Decoder", "not number or string")
}
}

type fuzzyBoolDecoder struct {
}

func (decoder *fuzzyBoolDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
valueType := iter.WhatIsNext()
var str string
switch valueType {
case jsoniter.BoolValue:
*((*bool)(ptr)) = iter.ReadBool()
case jsoniter.StringValue:
str = iter.ReadString()
switch str {
case "", "false", "0":
*((*bool)(ptr)) = false
default:
*((*bool)(ptr)) = true
}
case jsoniter.NumberValue:
fl := iter.ReadFloat64()
if int64(fl) == 0 {
*((*bool)(ptr)) = false
} else {
*((*bool)(ptr)) = true
}
case jsoniter.NilValue:
iter.Skip()
*((*bool)(ptr)) = false
default:
iter.ReportError("fuzzyBoolDecoder", "not bool, number or string")
}
}
42 changes: 42 additions & 0 deletions extra/fuzzy_decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,30 @@ func Test_any_to_string(t *testing.T) {
should.NotNil(jsoniter.UnmarshalFromString("{}", &val))
should.NotNil(jsoniter.UnmarshalFromString("[]", &val))
}

func Test_any_to_bool(t *testing.T) {
should := require.New(t)
var val bool
should.Nil(jsoniter.UnmarshalFromString(`"1"`, &val))
should.Equal(true, val)
should.Nil(jsoniter.UnmarshalFromString(`"true"`, &val))
should.Equal(true, val)
should.Nil(jsoniter.UnmarshalFromString(`""`, &val))
should.Equal(false, val)
should.Nil(jsoniter.UnmarshalFromString(`"0"`, &val))
should.Equal(false, val)
should.Nil(jsoniter.UnmarshalFromString(`"false"`, &val))
should.Equal(false, val)
should.Nil(jsoniter.UnmarshalFromString("1", &val))
should.Equal(true, val)
should.Nil(jsoniter.UnmarshalFromString("0", &val))
should.Equal(false, val)
should.Nil(jsoniter.UnmarshalFromString("null", &val))
should.Equal(false, val)
should.NotNil(jsoniter.UnmarshalFromString("{}", &val))
should.NotNil(jsoniter.UnmarshalFromString("[]", &val))
}

func Test_any_to_int64(t *testing.T) {
should := require.New(t)
var val int64
Expand Down Expand Up @@ -339,6 +363,24 @@ func Test_empty_array_as_object(t *testing.T) {
should.Equal(struct{}{}, val)
}

func Test_numeric_indexed_object_as_array(t *testing.T) {
should := require.New(t)
var val [1]string
should.Nil(jsoniter.UnmarshalFromString(`{"0":"a","2":"c"}`, &val))
should.Equal([1]string{"a"}, val)

should.Error(jsoniter.UnmarshalFromString(`{"2":"c","0":"a"}`, &val))
}

func Test_numeric_indexed_object_as_slice(t *testing.T) {
should := require.New(t)
var val []string
should.Nil(jsoniter.UnmarshalFromString(`{"0":"a","2":"c"}`, &val))
should.Equal([]string{"a", "c"}, val)

should.Error(jsoniter.UnmarshalFromString(`{"2":"c","0":"a"}`, &val))
}

func Test_bad_case(t *testing.T) {
var jsonstr = `
{
Expand Down