Skip to content

Commit

Permalink
feat: support marshal Inf/NaN as with option (#669)
Browse files Browse the repository at this point in the history
  • Loading branch information
liuq19 committed Jul 9, 2024
1 parent 121df0a commit 4adf417
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 4 deletions.
3 changes: 3 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
26 changes: 26 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
}
3 changes: 3 additions & 0 deletions encoder/encoder_native.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)


Expand Down
1 change: 1 addition & 0 deletions internal/encoder/alg/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
BitValidateString
BitNoValidateJSONMarshaler
BitNoEncoderNewline
BitEncodeNullForInfOrNan

BitPointerValue = 63
)
6 changes: 5 additions & 1 deletion internal/encoder/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions internal/encoder/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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<<alg.BitEncodeNullForInfOrNan) != 0 {
buf = append(buf, 'n', 'u', 'l', 'l')
continue
}
return vars.ERR_nan_or_infinite
}
buf = alg.F32toa(buf, v)
case ir.OP_f64:
v := *(*float64)(p)
if math.IsNaN(v) || math.IsInf(v, 0) {
if flags&(1<<alg.BitEncodeNullForInfOrNan) != 0 {
buf = append(buf, 'n', 'u', 'l', 'l')
continue
}
return vars.ERR_nan_or_infinite
}
buf = alg.F64toa(buf, v)
Expand Down
18 changes: 15 additions & 3 deletions internal/encoder/x86/assembler_regabi_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -869,12 +869,18 @@ func (self *Assembler) _asm_OP_f32(_ *ir.Instr) {
self.Emit("MOVL", jit.Ptr(_SP_p, 0), _AX) // MOVL (SP.p), AX
self.Emit("ANDL", jit.Imm(_FM_exp32), _AX) // ANDL $_FM_exp32, AX
self.Emit("XORL", jit.Imm(_FM_exp32), _AX) // XORL $_FM_exp32, AX
self.Sjmp("JZ", _LB_error_nan_or_infinite) // JZ _error_nan_or_infinite
self.Sjmp("JNZ", "_encode_normal_f32_{n}")// JNZ _encode_normal_f32_{n}
self.Emit("BTQ", jit.Imm(alg.BitEncodeNullForInfOrNan), _ARG_fv) // BTQ ${BitEncodeNullForInfOrNan}, fv
self.Sjmp("JNC", _LB_error_nan_or_infinite) // JNC _error_nan_or_infinite
self._asm_OP_null(nil)
self.Sjmp("JMP", "_encode_f32_end_{n}") // JMP _encode_f32_end_{n}
self.Link("_encode_normal_f32_{n}")
self.save_c() // SAVE $C_regs
self.rbuf_di() // MOVQ RP, DI
self.Emit("MOVSS", jit.Ptr(_SP_p, 0), _X0) // MOVSS (SP.p), X0
self.call_c(_F_f32toa) // CALL_C f64toa
self.call_c(_F_f32toa) // CALL_C f32toa
self.Emit("ADDQ", _AX, _RL) // ADDQ AX, RL
self.Link("_encode_f32_end_{n}")
}

func (self *Assembler) _asm_OP_f64(_ *ir.Instr) {
Expand All @@ -883,12 +889,18 @@ func (self *Assembler) _asm_OP_f64(_ *ir.Instr) {
self.Emit("MOVQ", jit.Imm(_FM_exp64), _CX) // MOVQ $_FM_exp64, CX
self.Emit("ANDQ", _CX, _AX) // ANDQ CX, AX
self.Emit("XORQ", _CX, _AX) // XORQ CX, AX
self.Sjmp("JZ", _LB_error_nan_or_infinite) // JZ _error_nan_or_infinite
self.Sjmp("JNZ", "_encode_normal_f64_{n}")// JNZ _encode_normal_f64_{n}
self.Emit("BTQ", jit.Imm(alg.BitEncodeNullForInfOrNan), _ARG_fv) // BTQ ${BitEncodeNullForInfOrNan}, fv
self.Sjmp("JNC", _LB_error_nan_or_infinite)// JNC _error_nan_or_infinite
self._asm_OP_null(nil)
self.Sjmp("JMP", "_encode_f64_end_{n}") // JMP _encode_f64_end_{n}
self.Link("_encode_normal_f64_{n}")
self.save_c() // SAVE $C_regs
self.rbuf_di() // MOVQ RP, DI
self.Emit("MOVSD", jit.Ptr(_SP_p, 0), _X0) // MOVSD (SP.p), X0
self.call_c(_F_f64toa) // CALL_C f64toa
self.Emit("ADDQ", _AX, _RL) // ADDQ AX, RL
self.Link("_encode_f64_end_{n}")
}

func (self *Assembler) _asm_OP_str(_ *ir.Instr) {
Expand Down
3 changes: 3 additions & 0 deletions sonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ func (cfg Config) Froze() API {
if cfg.NoEncoderNewline {
api.encoderOpts |= encoder.NoEncoderNewline
}
if cfg.EncodeNullForInfOrNan {
api.encoderOpts |= encoder.EncodeNullForInfOrNan
}

// configure decoder options:
if cfg.UseInt64 {
Expand Down

0 comments on commit 4adf417

Please sign in to comment.