From b661cd90e6393709d87f73e13d631129cfd8fdf9 Mon Sep 17 00:00:00 2001 From: ngicks Date: Thu, 23 Feb 2023 11:47:34 +0000 Subject: [PATCH 1/2] add ,string option tests --- type_tests/struct_tags_test.go | 22 +++++++++++++++++++ value_tests/struct_test.go | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/type_tests/struct_tags_test.go b/type_tests/struct_tags_test.go index ae4b80f2..fde6694b 100644 --- a/type_tests/struct_tags_test.go +++ b/type_tests/struct_tags_test.go @@ -146,6 +146,28 @@ func init() { (*struct { Field bool `json:",omitempty,string"` })(nil), + (*struct { + Str *string `json:",string"` + F32 *float32 `json:",string"` + F64 *float64 `json:",string"` + Int *int `json:",string"` + Uint *uint `json:",string"` + I16 *int16 `json:",string"` + I32 *int32 `json:",string"` + I64 *int64 `json:",string"` + U8 *uint8 `json:",string"` + U16 *uint16 `json:",string"` + U32 *uint32 `json:",string"` + U64 *uint64 `json:",string"` + Uptr *uintptr `json:",string"` + Bool *bool `json:",string"` + })(nil), + (*struct { + Struct struct{ Foo string } `json:",string"` + Arr [2]int `json:",string"` + Slice []string `json:",string"` + Map map[int]int `json:",string"` + })(nil), (*struct { Field bool `json:"中文"` })(nil), diff --git a/value_tests/struct_test.go b/value_tests/struct_test.go index 181e12fc..411d0eac 100644 --- a/value_tests/struct_test.go +++ b/value_tests/struct_test.go @@ -105,6 +105,18 @@ func init() { Asks [][2]float64 `json:"asks"` })(nil), input: `{"key_string": "KEYSTRING","type": "TYPE","asks": [[1e+66,1]]}`, + }, unmarshalCase{ + ptr: (*quote)(nil), + input: `{"Str":null,"F32":null,"F64":null,"Int":null,"Uint":null,"I16":null,"I32":null,"I64":null,"U8":null,"U16":null,"U32":null,"U64":null,"Uptr":null,"Bool":null}`, + }, unmarshalCase{ + ptr: (*quote)(nil), + input: `{"Str":"\"foo\""}`, + }, unmarshalCase{ + ptr: (*struct { + AnyStr interface{} `json:",string"` + AnyInt interface{} `json:",string"` + })(nil), + input: `{"AnyStr":"foo","AnyInt":123}`, }) marshalCases = append(marshalCases, struct { @@ -204,6 +216,14 @@ func init() { }{ "should not marshal", }, + quote{}, + struct { + AnyStr interface{} `json:",string"` + AnyInt interface{} `json:",string"` + }{ + AnyStr: "foo", + AnyInt: 123, + }, ) } @@ -245,3 +265,23 @@ type structOrder struct { orderB Field7 string } + +type quote struct { + // The ,string option applies only to fields of string, floating point, integer, + // or boolean types as per https://pkg.go.dev/encoding/json@go1.20.1. + // It is poorly or not totally documented that json.Marshal does not quote null. + Str *string `json:",string"` + F32 *float32 `json:",string"` + F64 *float64 `json:",string"` + Int *int `json:",string"` + Uint *uint `json:",string"` + I16 *int16 `json:",string"` + I32 *int32 `json:",string"` + I64 *int64 `json:",string"` + U8 *uint8 `json:",string"` + U16 *uint16 `json:",string"` + U32 *uint32 `json:",string"` + U64 *uint64 `json:",string"` + Uptr *uintptr `json:",string"` + Bool *bool `json:",string"` +} From e66e65e2467dfdb73d8d5a7f4f1c72e2c4918cb6 Mon Sep 17 00:00:00 2001 From: ngicks Date: Fri, 24 Feb 2023 09:28:42 +0000 Subject: [PATCH 2/2] fix quoting value even when not applicable --- reflect_extension.go | 23 ++++++++++++++----- reflect_struct_decoder.go | 47 +++++++++++++++++++++++++++++++++++++-- reflect_struct_encoder.go | 22 +++++++++++++++--- 3 files changed, 81 insertions(+), 11 deletions(-) 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..1fdaa954 100644 --- a/reflect_struct_decoder.go +++ b/reflect_struct_decoder.go @@ -1058,16 +1058,59 @@ 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 { + **((**string)(ptr)) = tempIter.ReadString() + } 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..bca308ee 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 && (ptr == nil || *(*unsafe.Pointer)(ptr) == nil) { + 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 && (ptr == nil || *(*unsafe.Pointer)(ptr) == nil) { + encoder.elemEncoder.Encode(ptr, stream) + return + } + tempStream := encoder.cfg.BorrowStream(nil) tempStream.Attachment = stream.Attachment defer encoder.cfg.ReturnStream(tempStream)