diff --git a/feature_reflect.go b/feature_reflect.go index ab4aa35..5865e9e 100644 --- a/feature_reflect.go +++ b/feature_reflect.go @@ -340,14 +340,10 @@ func createEncoderOfType(cfg *frozenConfig, prefix string, typ reflect.Type) Val } if typ.Implements(textMarshalerType) { checkIsEmpty := createCheckIsEmpty(cfg, typ) - templateInterface := reflect.New(typ).Elem().Interface() var encoder ValEncoder = &textMarshalerEncoder{ - templateInterface: extractInterface(templateInterface), + valType: reflect2.Type2(typ), checkIsEmpty: checkIsEmpty, } - if typ.Kind() == reflect.Ptr { - encoder = &OptionalEncoder{encoder} - } return encoder } if typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 { @@ -393,10 +389,7 @@ func createCheckIsEmpty(cfg *frozenConfig, typ reflect.Type) checkIsEmpty { case reflect.Bool: return &boolCodec{} case reflect.Interface: - if typ.NumMethod() == 0 { - return &emptyInterfaceCodec{} - } - return &nonEmptyInterfaceCodec{} + return &dynamicEncoder{reflect2.Type2(typ)} case reflect.Struct: return &structEncoder{typ: typ} case reflect.Array: @@ -492,10 +485,7 @@ func createEncoderOfSimpleType(cfg *frozenConfig, prefix string, typ reflect.Typ } return &boolCodec{} case reflect.Interface: - if typ.NumMethod() == 0 { - return &emptyInterfaceCodec{} - } - return &nonEmptyInterfaceCodec{} + return &dynamicEncoder{reflect2.Type2(typ)} case reflect.Struct: return encoderOfStruct(cfg, prefix, typ) case reflect.Array: diff --git a/feature_reflect_extension.go b/feature_reflect_extension.go index fd6c8a6..efcda5c 100644 --- a/feature_reflect_extension.go +++ b/feature_reflect_extension.go @@ -17,8 +17,6 @@ var extensions = []Extension{} // StructDescriptor describe how should we encode/decode the struct type StructDescriptor struct { - onePtrEmbedded bool - onePtrOptimization bool Type reflect.Type Fields []*Binding } @@ -297,25 +295,7 @@ func describeStruct(cfg *frozenConfig, prefix string, typ reflect.Type) *StructD return createStructDescriptor(cfg, typ, bindings, embeddedBindings) } func createStructDescriptor(cfg *frozenConfig, typ reflect.Type, bindings []*Binding, embeddedBindings []*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: - onePtrOptimization = true - case reflect.Struct: - onePtrOptimization = isStructOnePtr(firstField.Type) - } - } structDescriptor := &StructDescriptor{ - onePtrEmbedded: onePtrEmbedded, - onePtrOptimization: onePtrOptimization, Type: typ, Fields: bindings, } diff --git a/feature_reflect_native.go b/feature_reflect_native.go index 4a02585..57c388c 100644 --- a/feature_reflect_native.go +++ b/feature_reflect_native.go @@ -357,6 +357,20 @@ func (codec *nonEmptyInterfaceCodec) IsEmpty(ptr unsafe.Pointer) bool { return nonEmptyInterface.word == nil } +type dynamicEncoder struct { + valType reflect2.Type +} + +func (encoder *dynamicEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + obj := encoder.valType.UnsafeIndirect(ptr) + stream.WriteVal(obj) +} + +func (encoder *dynamicEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.valType.UnsafeIndirect(ptr) == nil +} + + type anyCodec struct { } @@ -390,7 +404,7 @@ func (codec *jsonNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { func (codec *jsonNumberCodec) Encode(ptr unsafe.Pointer, stream *Stream) { number := *((*json.Number)(ptr)) if len(number) == 0 { - stream.WriteRaw("0") + stream.writeByte('0') } else { stream.WriteRaw(string(number)) } @@ -418,7 +432,7 @@ func (codec *jsoniterNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { func (codec *jsoniterNumberCodec) Encode(ptr unsafe.Pointer, stream *Stream) { number := *((*Number)(ptr)) if len(number) == 0 { - stream.WriteRaw("0") + stream.writeByte('0') } else { stream.WriteRaw(string(number)) } @@ -603,15 +617,17 @@ func (encoder *marshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool { } type textMarshalerEncoder struct { - templateInterface emptyInterface + valType reflect2.Type checkIsEmpty checkIsEmpty } func (encoder *textMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { - templateInterface := encoder.templateInterface - templateInterface.word = ptr - realInterface := (*interface{})(unsafe.Pointer(&templateInterface)) - marshaler := (*realInterface).(encoding.TextMarshaler) + obj := encoder.valType.UnsafeIndirect(ptr) + if obj == nil { + stream.WriteNil() + return + } + marshaler := (obj).(encoding.TextMarshaler) bytes, err := marshaler.MarshalText() if err != nil { stream.Error = err diff --git a/feature_reflect_optional.go b/feature_reflect_optional.go index b1992dd..52def04 100644 --- a/feature_reflect_optional.go +++ b/feature_reflect_optional.go @@ -88,5 +88,22 @@ func (encoder *dereferenceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { } func (encoder *dereferenceEncoder) IsEmpty(ptr unsafe.Pointer) bool { - return encoder.ValueEncoder.IsEmpty(*((*unsafe.Pointer)(ptr))) + dePtr := *((*unsafe.Pointer)(ptr)) + if dePtr == nil { + return true + } + return encoder.ValueEncoder.IsEmpty(dePtr) +} + +func (encoder *dereferenceEncoder) IsEmbeddedPtrNil(ptr unsafe.Pointer) bool { + deReferenced := *((*unsafe.Pointer)(ptr)) + if deReferenced == nil { + return true + } + isEmbeddedPtrNil, converted := encoder.ValueEncoder.(IsEmbeddedPtrNil) + if !converted { + return false + } + fieldPtr := unsafe.Pointer(deReferenced) + return isEmbeddedPtrNil.IsEmbeddedPtrNil(fieldPtr) } \ No newline at end of file diff --git a/feature_reflect_object.go b/feature_reflect_struct_encoder.go similarity index 88% rename from feature_reflect_object.go rename to feature_reflect_struct_encoder.go index 911e722..c9e79e7 100644 --- a/feature_reflect_object.go +++ b/feature_reflect_struct_encoder.go @@ -42,8 +42,7 @@ func encoderOfStruct(cfg *frozenConfig, prefix string, typ reflect.Type) ValEnco }) } } - return &structEncoder{typ, structDescriptor.onePtrEmbedded, - structDescriptor.onePtrOptimization, finalOrderedFields} + return &structEncoder{typ, finalOrderedFields} } func resolveConflictBinding(cfg *frozenConfig, old, new *Binding) (ignoreOld, ignoreNew bool) { @@ -120,11 +119,22 @@ func (encoder *structFieldEncoder) IsEmpty(ptr unsafe.Pointer) bool { return encoder.fieldEncoder.IsEmpty(fieldPtr) } +func (encoder *structFieldEncoder) IsEmbeddedPtrNil(ptr unsafe.Pointer) bool { + isEmbeddedPtrNil, converted := encoder.fieldEncoder.(IsEmbeddedPtrNil) + if !converted { + return false + } + fieldPtr := unsafe.Pointer(uintptr(ptr) + encoder.field.Offset) + return isEmbeddedPtrNil.IsEmbeddedPtrNil(fieldPtr) +} + +type IsEmbeddedPtrNil interface { + IsEmbeddedPtrNil(ptr unsafe.Pointer) bool +} + type structEncoder struct { - typ reflect.Type - onePtrEmbedded bool - onePtrOptimization bool - fields []structFieldTo + typ reflect.Type + fields []structFieldTo } type structFieldTo struct { @@ -139,6 +149,9 @@ func (encoder *structEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { if field.encoder.omitempty && field.encoder.IsEmpty(ptr) { continue } + if field.encoder.IsEmbeddedPtrNil(ptr) { + continue + } if isNotFirst { stream.WriteMore() } diff --git a/value_tests/struct_test.go b/value_tests/struct_test.go index a956ac5..0f0cc03 100644 --- a/value_tests/struct_test.go +++ b/value_tests/struct_test.go @@ -92,7 +92,8 @@ func init() { }{&StructVarious{}}, struct { *StructVarious - }{}, + Field int + }{nil, 10}, struct { Field1 int Field2 [1]*float64 @@ -172,7 +173,6 @@ type CacheItem struct { MaxAge int `json:"cacheAge"` } - type orderA struct { Field2 string } @@ -193,4 +193,4 @@ type structOrder struct { Field3 string orderB Field7 string -} \ No newline at end of file +}