diff --git a/io/decoder.go b/io/decoder.go index b13f205..678ff67 100644 --- a/io/decoder.go +++ b/io/decoder.go @@ -6,7 +6,7 @@ | | | io/decoder.go | | | -| LastModified: Dec 13, 2023 | +| LastModified: Feb 7, 2024 | | Author: Ma Bingyao | | | \*________________________________________________________*/ @@ -63,6 +63,24 @@ const ( MapTypeSIMap ) +type StructType int8 + +const ( + // StructTypeStructPointer represents the default type is *T. + StructTypeStructPointer StructType = iota + // StructTypeStructObject represents the default type is T. + StructTypeStructObject +) + +type ListType int8 + +const ( + // ListTypeInterfaceSlice represents the default type is []interface{}. + ListTypeInterfaceSlice ListType = iota + // ListTypeSlice represents the default type is []T. + ListTypeSlice +) + const defaultBufferSize = 256 // Decoder is a io.Reader like object, with hprose specific read functions. @@ -79,6 +97,8 @@ type Decoder struct { LongType RealType MapType + StructType + ListType } // NewDecoder creates an Decoder instance from byte array. @@ -342,9 +362,11 @@ func (dec *Decoder) ResetBuffer() *Decoder { dec.head = 0 dec.tail = 0 dec.Error = nil - dec.MapType = MapTypeIIMap dec.RealType = RealTypeFloat64 dec.LongType = LongTypeInt + dec.MapType = MapTypeIIMap + dec.StructType = StructTypeStructPointer + dec.ListType = ListTypeInterfaceSlice return dec } diff --git a/io/interface_decoder_test.go b/io/interface_decoder_test.go new file mode 100644 index 0000000..dad89b7 --- /dev/null +++ b/io/interface_decoder_test.go @@ -0,0 +1,271 @@ +/*--------------------------------------------------------*\ +| | +| hprose | +| | +| Official WebSite: https://hprose.com | +| | +| io/interface_decoder_test.go | +| | +| LastModified: Feb 7, 2024 | +| Author: Ma Bingyao | +| | +\*________________________________________________________*/ + +package io_test + +import ( + "strings" + "testing" + + . "github.com/hprose/hprose-golang/v3/io" + "github.com/stretchr/testify/assert" +) + +func TestDecodeIntSliceToInterface(t *testing.T) { + sb := new(strings.Builder) + enc := NewEncoder(sb) + enc.Encode([]int{0, 1, 2, 3}) + + dec := NewDecoder(([]byte)(sb.String())) + var v interface{} + dec.Decode(&v) + assert.Equal(t, []interface{}{0, 1, 2, 3}, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, []int{0, 1, 2, 3}, v) +} + +func TestDecodeInt64SliceToInterface(t *testing.T) { + sb := new(strings.Builder) + enc := NewEncoder(sb) + enc.Encode([]int64{0, 1, 2, 3}) + + dec := NewDecoder(([]byte)(sb.String())) + var v interface{} + dec.Decode(&v) + assert.Equal(t, []interface{}{0, 1, 2, 3}, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, []int{0, 1, 2, 3}, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.LongType = LongTypeInt64 + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, []int64{0, 1, 2, 3}, v) +} + +func TestDecodeFloat64SliceToInterface(t *testing.T) { + sb := new(strings.Builder) + enc := NewEncoder(sb) + enc.Encode([]float64{0, 1, 2, 3}) + + dec := NewDecoder(([]byte)(sb.String())) + var v interface{} + dec.Decode(&v) + assert.Equal(t, []interface{}{float64(0), float64(1), float64(2), float64(3)}, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, []float64{0, 1, 2, 3}, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.RealType = RealTypeFloat32 + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, []float32{0, 1, 2, 3}, v) +} + +func TestDecodeStringSliceToInterface(t *testing.T) { + sb := new(strings.Builder) + enc := NewEncoder(sb) + enc.Encode([]string{"", "1", "2", "3"}) + + dec := NewDecoder(([]byte)(sb.String())) + var v interface{} + dec.Decode(&v) + assert.Equal(t, []interface{}{"", "1", "2", "3"}, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, []string{"", "1", "2", "3"}, v) +} + +func TestDecodeStructSliceToInterface(t *testing.T) { + type TestStruct struct { + A int + B bool + C string + D float32 + } + Register((*TestStruct)(nil)) + data := []TestStruct{ + {1, true, "1", 1}, + {2, true, "2", 2}, + {3, true, "3", 3}, + } + sb := new(strings.Builder) + enc := NewEncoder(sb) + enc.Encode(data) + + expectedData1 := []interface{}{ + &TestStruct{1, true, "1", 1}, + &TestStruct{2, true, "2", 2}, + &TestStruct{3, true, "3", 3}, + } + dec := NewDecoder(([]byte)(sb.String())) + var v interface{} + dec.Decode(&v) + assert.Equal(t, expectedData1, v) + + expectedData2 := []interface{}{ + TestStruct{1, true, "1", 1}, + TestStruct{2, true, "2", 2}, + TestStruct{3, true, "3", 3}, + } + + dec = NewDecoder(([]byte)(sb.String())) + dec.StructType = StructTypeStructObject + dec.Decode(&v) + assert.Equal(t, expectedData2, v) + + expectedData3 := []*TestStruct{ + {1, true, "1", 1}, + {2, true, "2", 2}, + {3, true, "3", 3}, + } + dec = NewDecoder(([]byte)(sb.String())) + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, expectedData3, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.StructType = StructTypeStructObject + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, data, v) + +} + +func TestDecodeBytesSliceToInterface(t *testing.T) { + data := [][]byte{ + {1, 2, 3}, + {4, 5, 6}, + nil, + {7, 8, 9}, + } + sb := new(strings.Builder) + enc := NewEncoder(sb) + enc.Encode(data) + dec := NewDecoder(([]byte)(sb.String())) + var v interface{} + dec.Decode(&v) + assert.Equal(t, []interface{}{ + []byte{1, 2, 3}, + []byte{4, 5, 6}, + nil, + []byte{7, 8, 9}, + }, v) + dec = NewDecoder(([]byte)(sb.String())) + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, data, v) +} + +func TestDecodeIntSliceSliceToInterface(t *testing.T) { + data := [][]int{ + {1, 2, 3}, + {4, 5, 6}, + nil, + {7, 8, 9}, + } + sb := new(strings.Builder) + enc := NewEncoder(sb) + enc.Encode(data) + + dec := NewDecoder(([]byte)(sb.String())) + var v interface{} + dec.Decode(&v) + assert.Equal(t, []interface{}{ + []interface{}{1, 2, 3}, + []interface{}{4, 5, 6}, + []interface{}(nil), + []interface{}{7, 8, 9}, + }, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, data, v) +} + +func TestDecodeMapSliceToInterface(t *testing.T) { + data := []map[string]interface{}{ + {"1": "1", "2": "2", "3": "3"}, + {"4": "4", "5": "5", "6": "6"}, + nil, + {"7": "7", "8": "8", "9": "9"}, + } + sb := new(strings.Builder) + enc := NewEncoder(sb) + enc.Encode(data) + + dec := NewDecoder(([]byte)(sb.String())) + var v interface{} + dec.Decode(&v) + assert.Equal(t, []interface{}{ + map[interface{}]interface{}{"1": "1", "2": "2", "3": "3"}, + map[interface{}]interface{}{"4": "4", "5": "5", "6": "6"}, + nil, + map[interface{}]interface{}{"7": "7", "8": "8", "9": "9"}, + }, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.MapType = MapTypeSIMap + dec.Decode(&v) + assert.Equal(t, []interface{}{ + map[string]interface{}{"1": "1", "2": "2", "3": "3"}, + map[string]interface{}{"4": "4", "5": "5", "6": "6"}, + nil, + map[string]interface{}{"7": "7", "8": "8", "9": "9"}, + }, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, []map[interface{}]interface{}{ + {"1": "1", "2": "2", "3": "3"}, + {"4": "4", "5": "5", "6": "6"}, + nil, + {"7": "7", "8": "8", "9": "9"}, + }, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.MapType = MapTypeSIMap + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, data, v) +} + +func TestDecodeInterfaceSliceToInterface(t *testing.T) { + sb := new(strings.Builder) + enc := NewEncoder(sb) + enc.Encode([]interface{}{1, 2.5, "3"}) + + dec := NewDecoder(([]byte)(sb.String())) + var v interface{} + dec.Decode(&v) + assert.Equal(t, []interface{}{1, 2.5, "3"}, v) + + dec = NewDecoder(([]byte)(sb.String())) + dec.ListType = ListTypeSlice + dec.Decode(&v) + assert.Equal(t, []interface{}{1, 2.5, "3"}, v) + +} diff --git a/io/interface_deocder.go b/io/interface_deocder.go index 92f581b..bb470f8 100644 --- a/io/interface_deocder.go +++ b/io/interface_deocder.go @@ -6,7 +6,7 @@ | | | io/interface_decoder.go | | | -| LastModified: Feb 20, 2022 | +| LastModified: Feb 7, 2024 | | Author: Ma Bingyao | | | \*________________________________________________________*/ @@ -17,6 +17,8 @@ import ( "fmt" "math" "math/big" + "reflect" + "unsafe" "github.com/modern-go/reflect2" ) @@ -78,7 +80,43 @@ func (dec *Decoder) decodeDoubleAsInterface(p *interface{}) { func (dec *Decoder) decodeListAsInterface(tag byte, p *interface{}) { var result []interface{} ifsdec.Decode(dec, &result, tag) - *p = result + n := len(result) + if n == 0 || dec.ListType == ListTypeInterfaceSlice { + *p = result + return + } + var t reflect.Type + for i := 0; i < n; i++ { + rt := reflect.TypeOf(result[i]) + if isNil(result[i]) || rt.Kind() == reflect.Invalid || rt.Kind() == reflect.Interface { + continue + } + if t == nil { + t = rt + } + if rt != t { + *p = result + return + } + } + if t == nil { + *p = result + return + } + st := reflect2.Type2(reflect.SliceOf(t)).(*reflect2.UnsafeSliceType) + s := st.UnsafeMakeSlice(n, n) + for i := 0; i < n; i++ { + if isNil(result[i]) { + continue + } + p := reflect2.PtrOf(result[i]) + if t.Kind() == reflect.Ptr || t.Kind() == reflect.Map { + st.UnsafeSetIndex(s, i, (unsafe.Pointer)(&p)) + } else { + st.UnsafeSetIndex(s, i, p) + } + } + *p = st.UnsafeIndirect(s) } func (dec *Decoder) decodeMapAsInterface(tag byte, p *interface{}) { diff --git a/io/struct_decoder.go b/io/struct_decoder.go index 94df7d1..a4249f9 100644 --- a/io/struct_decoder.go +++ b/io/struct_decoder.go @@ -6,7 +6,7 @@ | | | io/struct_decoder.go | | | -| LastModified: Dec 13, 2023 | +| LastModified: Feb 7, 2024 | | Author: Ma Bingyao | | | \*________________________________________________________*/ @@ -50,6 +50,9 @@ func (dec *Decoder) readObject(structInfo structInfo) interface{} { } } dec.Skip() + if dec.StructType == StructTypeStructObject { + return structInfo.t.UnsafeIndirect(ptr) + } return obj } diff --git a/io/struct_decoder_test.go b/io/struct_decoder_test.go index afc70f7..08733f6 100644 --- a/io/struct_decoder_test.go +++ b/io/struct_decoder_test.go @@ -6,7 +6,7 @@ | | | io/struct_encoder_test.go | | | -| LastModified: Mar 5, 2022 | +| LastModified: Feb 7, 2024 | | Author: Ma Bingyao | | | \*________________________________________________________*/ @@ -27,11 +27,10 @@ func TestDecodeEmptyToStruct(t *testing.T) { B bool `hprose:"-"` C string `json:"json,omitempty"` D float32 `json:",omitempty"` - e float64 } sb := &strings.Builder{} - enc := NewEncoder(sb).Simple(false) + enc := NewEncoder(sb) enc.Encode(nil) enc.Encode("") dec := NewDecoder(([]byte)(sb.String())) @@ -53,7 +52,7 @@ func TestDecodeStruct(t *testing.T) { src := TestStruct{1, true, "hello", 3.14, 2.718} sb := &strings.Builder{} - enc := NewEncoder(sb).Simple(false) + enc := NewEncoder(sb) enc.Encode(src) dec := NewDecoder(([]byte)(sb.String())) var ts TestStruct @@ -73,7 +72,7 @@ func TestDecodeStructPtr(t *testing.T) { hello := "hello" src := TestStruct{1, true, &hello, 3.14, 2.718} sb := &strings.Builder{} - enc := NewEncoder(sb).Simple(false) + enc := NewEncoder(sb) enc.Encode(src) dec := NewDecoder(([]byte)(sb.String())) var ts *TestStruct @@ -101,7 +100,7 @@ func TestDecodeMapAsObject(t *testing.T) { sb := &strings.Builder{} enc := NewEncoder(sb).Simple(false) enc.Encode(src) - dec := NewDecoder(([]byte)(sb.String())) + dec := NewDecoder(([]byte)(sb.String())).Simple(false) var ts *TestStruct dec.Decode(&ts) assert.Equal(t, &TestStruct{1, false, &hello, 3.14, 0}, ts) @@ -118,7 +117,7 @@ func TestDecodeAnonymousStruct(t *testing.T) { sb := &strings.Builder{} enc := NewEncoder(sb).Simple(false) enc.Encode(src) - dec := NewDecoder(([]byte)(sb.String())) + dec := NewDecoder(([]byte)(sb.String())).Simple(false) var ts struct { A int B bool `hprose:"-"` @@ -151,3 +150,46 @@ func TestDecodeStructWithDBTag(t *testing.T) { dec.Decode(&ts) assert.Equal(t, src, ts) } + +func TestDecodeStructAsInterface(t *testing.T) { + type TestStruct struct { + A int + B bool `hprose:"-"` + C string `json:"json,omitempty"` + D float32 `json:",omitempty"` + e float64 + } + Register((*TestStruct)(nil)) + src := TestStruct{1, true, "hello", 3.14, 2.718} + sb := &strings.Builder{} + enc := NewEncoder(sb).Simple(false) + enc.Encode(src) + dec := NewDecoder(([]byte)(sb.String())).Simple(false) + var ts interface{} + dec.Decode(&ts) + assert.Equal(t, &TestStruct{1, false, "hello", 3.14, 0}, ts) + dec = NewDecoder(([]byte)(sb.String())).Simple(false) + dec.StructType = StructTypeStructObject + dec.Decode(&ts) + assert.Equal(t, TestStruct{1, false, "hello", 3.14, 0}, ts) +} + +func TestDecodeSelfReferenceStructAsInterface(t *testing.T) { + type TestStruct struct { + A *TestStruct + } + Register((*TestStruct)(nil)) + src := TestStruct{} + src.A = &src + sb := &strings.Builder{} + enc := NewEncoder(sb).Simple(false) + enc.Encode(src) + dec := NewDecoder(([]byte)(sb.String())).Simple(false) + var ts interface{} + dec.Decode(&ts) + assert.Equal(t, &src, ts) + dec = NewDecoder(([]byte)(sb.String())).Simple(false) + dec.StructType = StructTypeStructObject + dec.Decode(&ts) + assert.Equal(t, src, ts) +}