mirror of
https://github.com/json-iterator/go.git
synced 2025-03-26 21:12:40 +02:00
#80 fix the case when embedded struct ptr is nil
This commit is contained in:
parent
4e608af2c7
commit
84fa033353
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user