Browse Source

#80 fix the case when embedded struct ptr is nil

Tao Wen 8 năm trước cách đây
mục cha
commit
84fa033353
4 tập tin đã thay đổi với 104 bổ sung47 xóa
  1. 24 4
      feature_reflect.go
  2. 29 7
      feature_reflect_extension.go
  3. 15 36
      feature_reflect_object.go
  4. 36 0
      jsoniter_interface_test.go

+ 24 - 4
feature_reflect.go

@@ -78,17 +78,37 @@ func (decoder *optionalDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
 		*((*unsafe.Pointer)(ptr)) = nil
 	} else {
 		if *((*unsafe.Pointer)(ptr)) == nil {
-			// pointer to null, we have to allocate memory to hold the value
+			//pointer to null, we have to allocate memory to hold the value
 			value := reflect.New(decoder.valueType)
-			decoder.valueDecoder.Decode(unsafe.Pointer(value.Pointer()), iter)
-			*((*uintptr)(ptr)) = value.Pointer()
+			newPtr := extractInterface(value.Interface()).word
+			decoder.valueDecoder.Decode(newPtr, iter)
+			*((*uintptr)(ptr)) = uintptr(newPtr)
 		} else {
-			// reuse existing instance
+			//reuse existing instance
 			decoder.valueDecoder.Decode(*((*unsafe.Pointer)(ptr)), iter)
 		}
 	}
 }
 
+type deferenceDecoder struct {
+	// only to deference a pointer
+	valueType    reflect.Type
+	valueDecoder ValDecoder
+}
+
+func (decoder *deferenceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+	if *((*unsafe.Pointer)(ptr)) == nil {
+		//pointer to null, we have to allocate memory to hold the value
+		value := reflect.New(decoder.valueType)
+		newPtr := extractInterface(value.Interface()).word
+		decoder.valueDecoder.Decode(newPtr, iter)
+		*((*uintptr)(ptr)) = uintptr(newPtr)
+	} else {
+		//reuse existing instance
+		decoder.valueDecoder.Decode(*((*unsafe.Pointer)(ptr)), iter)
+	}
+}
+
 type optionalEncoder struct {
 	valueEncoder ValEncoder
 }

+ 29 - 7
feature_reflect_extension.go

@@ -15,8 +15,10 @@ var fieldEncoders = map[string]ValEncoder{}
 var extensions = []Extension{}
 
 type StructDescriptor struct {
-	Type   reflect.Type
-	Fields []*Binding
+	onePtrEmbedded     bool
+	onePtrOptimization bool
+	Type               reflect.Type
+	Fields             []*Binding
 }
 
 func (structDescriptor *StructDescriptor) GetField(fieldName string) *Binding {
@@ -219,10 +221,12 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err
 					return nil, err
 				}
 				for _, binding := range structDescriptor.Fields {
+					cloneField := field
+					cloneField.Name = binding.Field.Name
 					binding.Encoder = &optionalEncoder{binding.Encoder}
-					binding.Encoder = &structFieldEncoder{&field, binding.Encoder, false}
-					binding.Decoder = &optionalDecoder{field.Type, binding.Decoder}
-					binding.Decoder = &structFieldDecoder{&field, binding.Decoder}
+					binding.Encoder = &structFieldEncoder{&cloneField, binding.Encoder, false}
+					binding.Decoder = &deferenceDecoder{field.Type.Elem(), binding.Decoder}
+					binding.Decoder = &structFieldDecoder{&cloneField, binding.Decoder}
 					if field.Offset == 0 {
 						headAnonymousBindings = append(headAnonymousBindings, binding)
 					} else {
@@ -267,9 +271,27 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err
 	return createStructDescriptor(cfg, typ, bindings, headAnonymousBindings, tailAnonymousBindings), nil
 }
 func createStructDescriptor(cfg *frozenConfig, typ reflect.Type, bindings []*Binding, headAnonymousBindings []*Binding, tailAnonymousBindings []*Binding) *StructDescriptor {
+	onePtrEmbedded := false
+	onePtrOptimization := false
+	if typ.NumField() == 1 {
+		firstField := typ.Field(0)
+		switch firstField.Type.Kind() {
+		case reflect.Ptr:
+			if firstField.Anonymous && firstField.Type.Elem().Kind() == reflect.Struct {
+				onePtrEmbedded = true
+			}
+			fallthrough
+		case reflect.Map:
+			fallthrough
+		case reflect.Slice:
+			onePtrOptimization = true
+		}
+	}
 	structDescriptor := &StructDescriptor{
-		Type:   typ,
-		Fields: bindings,
+		onePtrEmbedded:     onePtrEmbedded,
+		onePtrOptimization: onePtrOptimization,
+		Type:               typ,
+		Fields:             bindings,
 	}
 	for _, extension := range extensions {
 		extension.UpdateStructDescriptor(structDescriptor)

+ 15 - 36
feature_reflect_object.go

@@ -36,7 +36,7 @@ func encoderOfStruct(cfg *frozenConfig, typ reflect.Type) (ValEncoder, error) {
 			finalOrderedFields = append(finalOrderedFields, *structFieldTo)
 		}
 	}
-	return &structEncoder{finalOrderedFields}, nil
+	return &structEncoder{structDescriptor.onePtrEmbedded,structDescriptor.onePtrOptimization,finalOrderedFields}, nil
 }
 
 func decoderOfStruct(cfg *frozenConfig, typ reflect.Type) (ValDecoder, error) {
@@ -992,6 +992,8 @@ func (encoder *structFieldEncoder) IsEmpty(ptr unsafe.Pointer) bool {
 }
 
 type structEncoder struct {
+	onePtrEmbedded bool
+	onePtrOptimization bool
 	fields []structFieldTo
 }
 
@@ -1018,44 +1020,21 @@ func (encoder *structEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
 }
 
 func (encoder *structEncoder) EncodeInterface(val interface{}, stream *Stream) {
-	var encoderToUse ValEncoder
-	encoderToUse = encoder
-	if len(encoder.fields) == 1 {
-		firstFieldName := encoder.fields[0].toName
-		firstField := encoder.fields[0].encoder
-		firstEncoder := firstField.fieldEncoder
-		firstEncoderName := reflect.TypeOf(firstEncoder).String()
-		// interface{} has inline optimization for this case
-		if firstEncoderName == "*jsoniter.optionalEncoder" {
-			encoderToUse = &structEncoder{
-				fields: []structFieldTo{
-					{
-						toName: firstFieldName,
-						encoder: &structFieldEncoder{
-							field:        firstField.field,
-							fieldEncoder: firstEncoder.(*optionalEncoder).valueEncoder,
-							omitempty:    firstField.omitempty,
-						},
-					},
-				},
-			}
-			e := (*emptyInterface)(unsafe.Pointer(&val))
-			if e.word == nil {
-				stream.WriteObjectStart()
-				stream.WriteObjectField(firstFieldName)
-				stream.WriteNil()
-				stream.WriteObjectEnd()
-				return
-			}
-			if reflect.TypeOf(val).Kind() == reflect.Ptr {
-				encoderToUse.Encode(unsafe.Pointer(&e.word), stream)
-			} else {
-				encoderToUse.Encode(e.word, stream)
-			}
+	e := (*emptyInterface)(unsafe.Pointer(&val))
+	if encoder.onePtrOptimization {
+		if e.word == nil && encoder.onePtrEmbedded {
+			stream.WriteObjectStart()
+			stream.WriteObjectEnd()
 			return
 		}
+		ptr := uintptr(e.word)
+		e.word = unsafe.Pointer(&ptr)
+	}
+	if reflect.TypeOf(val).Kind() == reflect.Ptr {
+		encoder.Encode(unsafe.Pointer(&e.word), stream)
+	} else {
+		encoder.Encode(e.word, stream)
 	}
-	WriteToStream(val, stream, encoderToUse)
 }
 
 func (encoder *structEncoder) IsEmpty(ptr unsafe.Pointer) bool {

+ 36 - 0
jsoniter_interface_test.go

@@ -197,6 +197,42 @@ func Test_struct_with_one_nil(t *testing.T) {
 	should.Equal(`{"F":null}`, output)
 }
 
+func Test_struct_with_one_nil_embedded(t *testing.T) {
+	type Parent struct {
+		Field1 string
+		Field2 string
+	}
+	type TestObject struct {
+		*Parent
+	}
+	obj := TestObject{}
+	should := require.New(t)
+	bytes, err := json.Marshal(obj)
+	should.Nil(err)
+	should.Equal("{}", string(bytes))
+	output, err := MarshalToString(obj)
+	should.Nil(err)
+	should.Equal(`{}`, output)
+}
+
+func Test_struct_with_not_nil_embedded(t *testing.T) {
+	type Parent struct {
+		Field0 string
+		Field1 []string
+		Field2 map[string]interface{}
+	}
+	type TestObject struct {
+		*Parent
+	}
+	should := require.New(t)
+	var obj TestObject
+	err := UnmarshalFromString(`{"Field0":"1","Field1":null,"Field2":{"K":"V"}}`, &obj)
+	should.Nil(err)
+	should.Nil(obj.Field1)
+	should.Equal(map[string]interface{}{"K": "V"}, obj.Field2)
+	should.Equal("1", obj.Field0)
+}
+
 func Test_array_with_one_nil(t *testing.T) {
 	obj := [1]*float64{nil}
 	should := require.New(t)