diff --git a/api.go b/api.go index 093329127..6a1255688 100644 --- a/api.go +++ b/api.go @@ -77,6 +77,9 @@ type Config struct { // NoEncoderNewline indicates that the encoder should not add a newline after every message NoEncoderNewline bool + + // Encode Infinity or Nan float into `null`, instead of returning an error. + EncodeNullForInfOrNan bool } var ( diff --git a/encode_test.go b/encode_test.go index 09639cfaa..64a5e68b0 100644 --- a/encode_test.go +++ b/encode_test.go @@ -1198,4 +1198,30 @@ func TestEncoder_RandomInvalidUtf8(t *testing.T) { testEncodeInvalidUtf8(t, genRandJsonBytes(maxLen)) testEncodeInvalidUtf8(t, genRandJsonRune(maxLen)) } +} + +func TestMarshalInfOrNan(t *testing.T) { + tests := [] interface{}{ + math.Inf(1), math.Inf(-1), math.NaN(), float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), []interface{}{math.Inf(1), math.Inf(-1), math.NaN(), float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN())}, + + []float64{math.Inf(1), math.Inf(-1), math.NaN()}, + []float32{float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN())}, + } + + allowNanInf := Config { + EncodeNullForInfOrNan: true, + }.Froze() + for _, tt := range tests { + b, err := allowNanInf.Marshal(tt) + assert.Nil(t, err) + if len(b) == 4 { + assert.Equal(t, string(b), "null") + } else { + println(string(b)) + } + + b, err = Marshal(tt) + assert.NotNil(t, err) + assert.True(t, strings.Contains(err.Error(), "json: unsupported value: NaN or ±Infinite")) + } } \ No newline at end of file diff --git a/encoder/encoder_native.go b/encoder/encoder_native.go index 7067db66a..737ec001f 100644 --- a/encoder/encoder_native.go +++ b/encoder/encoder_native.go @@ -70,6 +70,9 @@ const ( // CompatibleWithStd is used to be compatible with std encoder. CompatibleWithStd Options = encoder.CompatibleWithStd + + // Encode Infinity or Nan float into `null`, instead of returning an error. + EncodeNullForInfOrNan Options = encoder.EncodeNullForInfOrNan ) diff --git a/internal/encoder/alg/opts.go b/internal/encoder/alg/opts.go index 3dad938b5..c19e2de4e 100644 --- a/internal/encoder/alg/opts.go +++ b/internal/encoder/alg/opts.go @@ -25,6 +25,7 @@ const ( BitValidateString BitNoValidateJSONMarshaler BitNoEncoderNewline + BitEncodeNullForInfOrNan BitPointerValue = 63 ) diff --git a/internal/encoder/encoder.go b/internal/encoder/encoder.go index 4cd03f803..1efbcca5c 100644 --- a/internal/encoder/encoder.go +++ b/internal/encoder/encoder.go @@ -53,7 +53,8 @@ const ( NoQuoteTextMarshaler Options = 1 << alg.BitNoQuoteTextMarshaler // NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}', - // instead of 'null' + // instead of 'null'. + // NOTE: The priority of this option is lower than json tag `omitempty`. NoNullSliceOrMap Options = 1 << alg.BitNoNullSliceOrMap // ValidateString indicates that encoder should validate the input string @@ -69,6 +70,9 @@ const ( // CompatibleWithStd is used to be compatible with std encoder. CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler + + // Encode Infinity or Nan float into `null`, instead of returning an error. + EncodeNullForInfOrNan Options = 1 << alg.BitEncodeNullForInfOrNan ) // Encoder represents a specific set of encoder configurations. diff --git a/internal/encoder/vm/vm.go b/internal/encoder/vm/vm.go index bde62521c..b56145903 100644 --- a/internal/encoder/vm/vm.go +++ b/internal/encoder/vm/vm.go @@ -159,12 +159,20 @@ func Execute(b *[]byte, p unsafe.Pointer, s *vars.Stack, flags uint64, prog *ir. case ir.OP_f32: v := *(*float32)(p) if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) { + if flags&(1<