diff --git a/reflect_extension.go b/reflect_extension.go index 74a97bfe..ed1a21eb 100644 --- a/reflect_extension.go +++ b/reflect_extension.go @@ -2,12 +2,13 @@ package jsoniter import ( "fmt" - "github.com/modern-go/reflect2" "reflect" "sort" "strings" "unicode" "unsafe" + + "github.com/modern-go/reflect2" ) var typeDecoders = map[string]ValDecoder{} @@ -448,12 +449,22 @@ func processTags(structDescriptor *StructDescriptor, cfg *frozenConfig) { if tagPart == "omitempty" { shouldOmitEmpty = true } else if tagPart == "string" { - if binding.Field.Type().Kind() == reflect.String { - binding.Decoder = &stringModeStringDecoder{binding.Decoder, cfg} - binding.Encoder = &stringModeStringEncoder{binding.Encoder, cfg} - } else { + fieldType := binding.Field.Type() + isPointer := false + if fieldType.Kind() == reflect.Pointer { + isPointer = true + fieldType = reflect2.Type2(fieldType.Type1().Elem()) + } + switch fieldType.Kind() { + case reflect.String: + binding.Decoder = &stringModeStringDecoder{isPointer, binding.Decoder, cfg} + binding.Encoder = &stringModeStringEncoder{isPointer, binding.Encoder, cfg} + case reflect.Float32, reflect.Float64, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Bool: binding.Decoder = &stringModeNumberDecoder{binding.Decoder} - binding.Encoder = &stringModeNumberEncoder{binding.Encoder} + binding.Encoder = &stringModeNumberEncoder{isPointer, binding.Encoder} } } } diff --git a/reflect_struct_decoder.go b/reflect_struct_decoder.go index 92ae912d..8e82e4a9 100644 --- a/reflect_struct_decoder.go +++ b/reflect_struct_decoder.go @@ -1058,16 +1058,60 @@ func (decoder *structFieldDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { } type stringModeStringDecoder struct { + isPointer bool // true indicates *string field type. elemDecoder ValDecoder cfg *frozenConfig } func (decoder *stringModeStringDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { decoder.elemDecoder.Decode(ptr, iter) - str := *((*string)(ptr)) + + if iter.Error != nil { + return + } + + var str string + if decoder.isPointer { + if *((**string)(ptr)) == nil { + return + } + str = **((**string)(ptr)) + } else { + str = *((*string)(ptr)) + } + + if len(str) < 2 { + iter.ReportError( + "stringModeStringDecoder", + `expect len(s) >= 2, but found `+str, + ) + return + } + if str[0] != '"' || str[len(str)-1] != '"' { + var idx int + if str[0] == '"' { + idx = len(str) - 1 + } + iter.ReportError( + "stringModeStringDecoder", + `expect ", but found `+string(str[idx]), + ) + return + } + tempIter := decoder.cfg.BorrowIterator([]byte(str)) defer decoder.cfg.ReturnIterator(tempIter) - *((*string)(ptr)) = tempIter.ReadString() + + if decoder.isPointer { + unescaped := tempIter.ReadString() + *((**string)(ptr)) = &unescaped + } else { + *((*string)(ptr)) = tempIter.ReadString() + } + + if tempIter.Error != nil { + iter.Error = tempIter.Error + } } type stringModeNumberDecoder struct { diff --git a/reflect_struct_encoder.go b/reflect_struct_encoder.go index 152e3ef5..73cf7b25 100644 --- a/reflect_struct_encoder.go +++ b/reflect_struct_encoder.go @@ -2,10 +2,11 @@ package jsoniter import ( "fmt" - "github.com/modern-go/reflect2" "io" "reflect" "unsafe" + + "github.com/modern-go/reflect2" ) func encoderOfStruct(ctx *ctx, typ reflect2.Type) ValEncoder { @@ -180,13 +181,22 @@ func (encoder *emptyStructEncoder) IsEmpty(ptr unsafe.Pointer) bool { } type stringModeNumberEncoder struct { + isPointer bool elemEncoder ValEncoder } func (encoder *stringModeNumberEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { - stream.writeByte('"') + shouldQuote := true + if encoder.isPointer && ((*reflect2.UnsafePtrType)(nil)).UnsafeIsNil(ptr) { + shouldQuote = false + } + if shouldQuote { + stream.writeByte('"') + } encoder.elemEncoder.Encode(ptr, stream) - stream.writeByte('"') + if shouldQuote { + stream.writeByte('"') + } } func (encoder *stringModeNumberEncoder) IsEmpty(ptr unsafe.Pointer) bool { @@ -194,11 +204,17 @@ func (encoder *stringModeNumberEncoder) IsEmpty(ptr unsafe.Pointer) bool { } type stringModeStringEncoder struct { + isPointer bool elemEncoder ValEncoder cfg *frozenConfig } func (encoder *stringModeStringEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + if encoder.isPointer && ((*reflect2.UnsafePtrType)(nil)).UnsafeIsNil(ptr) { + encoder.elemEncoder.Encode(ptr, stream) + return + } + tempStream := encoder.cfg.BorrowStream(nil) tempStream.Attachment = stream.Attachment defer encoder.cfg.ReturnStream(tempStream)