diff --git a/api.go b/api.go index 406715eca..30a254147 100644 --- a/api.go +++ b/api.go @@ -94,6 +94,13 @@ type Config struct { // Encode Infinity or Nan float into `null`, instead of returning an error. EncodeNullForInfOrNan bool + + // Uint64 or Int64 into strings on Marshal + Uint64ToString bool + Int64ToString bool + // UintExceedToString when intSize == 64, if the value of type uint or uint64 exceeds MaxInt64, + // automatically convert it to a string. + UintExceedToString bool } var ( diff --git a/encode_test.go b/encode_test.go index f97244a2c..1eb1ed8f7 100644 --- a/encode_test.go +++ b/encode_test.go @@ -1224,4 +1224,257 @@ func TestMarshalInfOrNan(t *testing.T) { assert.NotNil(t, err) assert.True(t, strings.Contains(err.Error(), "json: unsupported value: NaN or ±Infinite")) } -} \ No newline at end of file +} + +func TestUint64OrInt64ToString(t *testing.T) { + int64ptr := int64(1) + intptr := int(1) + uintPtr := uint(1) + + uint64Lptr := uint64(9223372036854775806) + uint64Eptr := uint64(9223372036854775807) + uint64Gptr := uint64(9223372036854775808) + uintLptr := uint(9223372036854775806) + uintEptr := uint(9223372036854775807) + uintGptr := uint(9223372036854775808) + cases := []struct { + name string + val interface{} + exceptUint64ToStr string + exceptInt64ToStr string + exceptUIntExToStr string + exceptFalse string + }{ + { + name: "normal_map", + val: map[string]interface{}{ + "int": int(12), + "uint": uint(99), + "int64": int64(34), + "uint64": uint64(56), + "exceedUint": uint(9223372036854775808), + "exceedUint64": uint64(9223372036854775808), + "eqMaxInt64Uint64": uint64(9223372036854775807), + "eqMaxInt64Uint": uint(9223372036854775807), + "lsMaxInt64Uint64": uint64(9223372036854775806), + "lsMaxInt64Uint": uint(9223372036854775806), + }, + exceptUint64ToStr: `{"eqMaxInt64Uint":9223372036854775807,"eqMaxInt64Uint64":"9223372036854775807",`+ + `"exceedUint":9223372036854775808,"exceedUint64":"9223372036854775808",`+ + `"int":12,"int64":34,`+ + `"lsMaxInt64Uint":9223372036854775806,"lsMaxInt64Uint64":"9223372036854775806",`+ + `"uint":99,"uint64":"56"}`, + exceptInt64ToStr: `{"eqMaxInt64Uint":9223372036854775807,"eqMaxInt64Uint64":9223372036854775807,`+ + `"exceedUint":9223372036854775808,"exceedUint64":9223372036854775808,`+ + `"int":12,"int64":"34",`+ + `"lsMaxInt64Uint":9223372036854775806,"lsMaxInt64Uint64":9223372036854775806,`+ + `"uint":99,"uint64":56}`, + exceptUIntExToStr: `{"eqMaxInt64Uint":9223372036854775807,"eqMaxInt64Uint64":9223372036854775807,`+ + `"exceedUint":"9223372036854775808","exceedUint64":"9223372036854775808",`+ + `"int":12,"int64":34,`+ + `"lsMaxInt64Uint":9223372036854775806,"lsMaxInt64Uint64":9223372036854775806,`+ + `"uint":99,"uint64":56}`, + exceptFalse: `{"eqMaxInt64Uint":9223372036854775807,"eqMaxInt64Uint64":9223372036854775807,`+ + `"exceedUint":9223372036854775808,"exceedUint64":9223372036854775808,`+ + `"int":12,"int64":34,`+ + `"lsMaxInt64Uint":9223372036854775806,"lsMaxInt64Uint64":9223372036854775806,`+ + `"uint":99,"uint64":56}`, + }, + { + name: "int64_key_map", + val: map[int64]interface{}{ + int64(1): 1, + int64(2): 2, + int64(3): 3, + int64(4): 4, + }, + exceptUint64ToStr: `{"1":1,"2":2,"3":3,"4":4}`, + exceptInt64ToStr: `{"1":1,"2":2,"3":3,"4":4}`, + exceptUIntExToStr: `{"1":1,"2":2,"3":3,"4":4}`, + exceptFalse: `{"1":1,"2":2,"3":3,"4":4}`, + }, + { + name: "uint64_key_map", + val: map[uint64]interface{}{ + uint64(12): 1, + uint64(99): 1, + uint64(34): 1, + uint64(9223372036854775806): 1, + uint64(9223372036854775807): 1, + uint64(9223372036854775808): 1, + }, + exceptUint64ToStr: `{"12":1,"34":1,"9223372036854775806":1,"9223372036854775807":1,"9223372036854775808":1,"99":1}`, + exceptInt64ToStr: `{"12":1,"34":1,"9223372036854775806":1,"9223372036854775807":1,"9223372036854775808":1,"99":1}`, + exceptUIntExToStr: `{"12":1,"34":1,"9223372036854775806":1,"9223372036854775807":1,"9223372036854775808":1,"99":1}`, + exceptFalse: `{"12":1,"34":1,"9223372036854775806":1,"9223372036854775807":1,"9223372036854775808":1,"99":1}`, + }, + { + name: "uint_key_map", + val: map[uint]interface{}{ + uint(12): int(12), + uint(99): uint(99), + uint(34): int64(34), + uint(56): uint64(56), + uint(9223372036854775806): uint64(10), + uint(9223372036854775807): uint64(10), + uint(9223372036854775808): uint64(10), + }, + exceptUint64ToStr: `{"12":12,"34":34,"56":"56","9223372036854775806":"10","9223372036854775807":"10","9223372036854775808":"10","99":99}`, + exceptInt64ToStr: `{"12":12,"34":"34","56":56,"9223372036854775806":10,"9223372036854775807":10,"9223372036854775808":10,"99":99}`, + exceptUIntExToStr: `{"12":12,"34":34,"56":56,"9223372036854775806":10,"9223372036854775807":10,"9223372036854775808":10,"99":99}`, + exceptFalse: `{"12":12,"34":34,"56":56,"9223372036854775806":10,"9223372036854775807":10,"9223372036854775808":10,"99":99}`, + }, + { + name: "normal_struct", + val: struct { + Int int `json:"int"` + Int64 int64 `json:"int64"` + Uint uint `json:"uint"` + UintL uint `json:"uintl"` + UintE uint `json:"uinte"` + UintG uint `json:"uintg"` + Uint64 uint64 `json:"uint64"` + Uint64L uint64 `json:"uint64l"` + Uint64E uint64 `json:"uint64e"` + Uint64G uint64 `json:"uint64g"` + }{ + Int: int(1), + Int64: int64(2), + Uint: uint(1), + UintL: uint(9223372036854775806), + UintE: uint(9223372036854775807), + UintG: uint(9223372036854775808), + Uint64: uint64(1), + Uint64L: uint64(9223372036854775806), + Uint64E: uint64(9223372036854775807), + Uint64G: uint64(9223372036854775808), + }, + exceptUint64ToStr: `{"int":1,"int64":2,`+ + `"uint":1,"uintl":9223372036854775806,"uinte":9223372036854775807,"uintg":9223372036854775808,`+ + `"uint64":"1","uint64l":"9223372036854775806","uint64e":"9223372036854775807","uint64g":"9223372036854775808"}`, + exceptInt64ToStr: `{"int":1,"int64":"2",`+ + `"uint":1,"uintl":9223372036854775806,"uinte":9223372036854775807,"uintg":9223372036854775808,`+ + `"uint64":1,"uint64l":9223372036854775806,"uint64e":9223372036854775807,"uint64g":9223372036854775808}`, + exceptUIntExToStr: `{"int":1,"int64":2,`+ + `"uint":1,"uintl":9223372036854775806,"uinte":9223372036854775807,"uintg":"9223372036854775808",`+ + `"uint64":1,"uint64l":9223372036854775806,"uint64e":9223372036854775807,"uint64g":"9223372036854775808"}`, + exceptFalse: `{"int":1,"int64":2,`+ + `"uint":1,"uintl":9223372036854775806,"uinte":9223372036854775807,"uintg":9223372036854775808,`+ + `"uint64":1,"uint64l":9223372036854775806,"uint64e":9223372036854775807,"uint64g":9223372036854775808}`, + }, + { + name: "normal_slice", + val: []interface{}{ + int(12), uint(99), int64(34), uint64(56), uint(9223372036854775806), uint(9223372036854775807), uint(9223372036854775808), + uint64(9223372036854775806), uint64(9223372036854775807), uint64(9223372036854775808), + }, + exceptUint64ToStr: `[12,99,34,"56",`+ + `9223372036854775806,9223372036854775807,9223372036854775808,`+ + `"9223372036854775806","9223372036854775807","9223372036854775808"]`, + exceptInt64ToStr: `[12,99,"34",56,`+ + `9223372036854775806,9223372036854775807,9223372036854775808,`+ + `9223372036854775806,9223372036854775807,9223372036854775808]`, + exceptUIntExToStr: `[12,99,34,56,`+ + `9223372036854775806,9223372036854775807,"9223372036854775808",`+ + `9223372036854775806,9223372036854775807,"9223372036854775808"]`, + exceptFalse: `[12,99,34,56,`+ + `9223372036854775806,9223372036854775807,9223372036854775808,`+ + `9223372036854775806,9223372036854775807,9223372036854775808]`, + }, + { + name: "single_int64", + val: int64(34), + exceptUint64ToStr: `34`, + exceptInt64ToStr: `"34"`, + exceptUIntExToStr: `34`, + exceptFalse: `34`, + }, + { + name: "single_uint64", + val: uint64(56), + exceptUint64ToStr: `"56"`, + exceptInt64ToStr: `56`, + exceptUIntExToStr: `56`, + exceptFalse: `56`, + }, + { + name: "single_uint64g", + val: uint64(9223372036854775808), + exceptUint64ToStr: `"9223372036854775808"`, + exceptInt64ToStr: `9223372036854775808`, + exceptUIntExToStr: `"9223372036854775808"`, + exceptFalse: `9223372036854775808`, + }, + { + name: "single_uintg", + val: uint64(9223372036854775808), + exceptUint64ToStr: `"9223372036854775808"`, + exceptInt64ToStr: `9223372036854775808`, + exceptUIntExToStr: `"9223372036854775808"`, + exceptFalse: `9223372036854775808`, + }, + { + name: "normal_struct_ptr_val", + val: struct { + Int *int `json:"int"` + Int64 *int64 `json:"int64"` + Uint *uint `json:"uint"` + UintL *uint `json:"uintl"` + UintE *uint `json:"uinte"` + UintG *uint `json:"uintg"` + Uint64L *uint64 `json:"uint64l"` + Uint64E *uint64 `json:"uint64e"` + Uint64G *uint64 `json:"uint64g"` + }{ + Int: &intptr, + Int64: &int64ptr, + Uint: &uintPtr, + UintL: &uintLptr, + UintE: &uintEptr, + UintG: &uintGptr, + Uint64L: &uint64Lptr, + Uint64E: &uint64Eptr, + Uint64G: &uint64Gptr, + }, + exceptUint64ToStr: `{"int":1,"int64":1,"uint":1,`+ + `"uintl":9223372036854775806,"uinte":9223372036854775807,"uintg":9223372036854775808,`+ + `"uint64l":"9223372036854775806","uint64e":"9223372036854775807","uint64g":"9223372036854775808"}`, + exceptInt64ToStr: `{"int":1,"int64":"1","uint":1,`+ + `"uintl":9223372036854775806,"uinte":9223372036854775807,"uintg":9223372036854775808,`+ + `"uint64l":9223372036854775806,"uint64e":9223372036854775807,"uint64g":9223372036854775808}`, + exceptUIntExToStr: `{"int":1,"int64":1,"uint":1,`+ + `"uintl":9223372036854775806,"uinte":9223372036854775807,"uintg":"9223372036854775808",`+ + `"uint64l":9223372036854775806,"uint64e":9223372036854775807,"uint64g":"9223372036854775808"}`, + exceptFalse: `{"int":1,"int64":1,"uint":1,`+ + `"uintl":9223372036854775806,"uinte":9223372036854775807,"uintg":9223372036854775808,`+ + `"uint64l":9223372036854775806,"uint64e":9223372036854775807,"uint64g":9223372036854775808}`, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + t.Run("uint64ToStr", func(t *testing.T) { + b, e := Config{Uint64ToString: true, SortMapKeys: true}.Froze().Marshal(c.val) + assert.Nil(t, e) + assert.Equal(t, c.exceptUint64ToStr, string(b)) + }) + + t.Run("int64ToStr", func(t *testing.T) { + b, e := Config{Int64ToString: true, SortMapKeys: true}.Froze().Marshal(c.val) + assert.Nil(t, e) + assert.Equal(t, c.exceptInt64ToStr, string(b)) + }) + t.Run("uintExceedToStr", func(t *testing.T) { + b, e := Config{UintExceedToString: true, SortMapKeys: true}.Froze().Marshal(c.val) + assert.Nil(t, e) + assert.Equal(t, c.exceptUIntExToStr, string(b)) + }) + + t.Run("noIntegerToStr", func(t *testing.T) { + b, e := Config{SortMapKeys: true}.Froze().Marshal(c.val) + assert.Nil(t, e) + assert.Equal(t, c.exceptFalse, string(b)) + }) + }) + } +} diff --git a/encoder/encoder_native.go b/encoder/encoder_native.go index b300ebf08..ab6e2b389 100644 --- a/encoder/encoder_native.go +++ b/encoder/encoder_native.go @@ -73,6 +73,13 @@ const ( // Encode Infinity or Nan float into `null`, instead of returning an error. EncodeNullForInfOrNan Options = encoder.EncodeNullForInfOrNan + + // Uint64 or Int64 into strings on Marshal + Uint64ToString Options = encoder.Uint64ToString + Int64ToString Options = encoder.Int64ToString + // UintExceedToString when intSize == 64, if the value of type uint or uint64 exceeds MaxInt64, + // automatically convert it to a string. + UintExceedToString Options = encoder.UintExceedToString ) diff --git a/internal/encoder/alg/opts.go b/internal/encoder/alg/opts.go index c19e2de4e..22fa649a9 100644 --- a/internal/encoder/alg/opts.go +++ b/internal/encoder/alg/opts.go @@ -25,7 +25,10 @@ const ( BitValidateString BitNoValidateJSONMarshaler BitNoEncoderNewline - BitEncodeNullForInfOrNan + BitEncodeNullForInfOrNan + BitUint64ToString + BitInt64ToString + BitUintExceedToString BitPointerValue = 63 ) diff --git a/internal/encoder/compiler.go b/internal/encoder/compiler.go index 902fbc98b..031e94ae4 100644 --- a/internal/encoder/compiler.go +++ b/internal/encoder/compiler.go @@ -179,7 +179,7 @@ func (self *Compiler) compileOps(p *ir.Program, sp int, vt reflect.Type) { case reflect.Bool: p.Add(ir.OP_bool) case reflect.Int: - p.Add(ir.OP_int()) + p.Add(ir.OP_int(), ir.OP_i) case reflect.Int8: p.Add(ir.OP_i8) case reflect.Int16: @@ -189,7 +189,7 @@ func (self *Compiler) compileOps(p *ir.Program, sp int, vt reflect.Type) { case reflect.Int64: p.Add(ir.OP_i64) case reflect.Uint: - p.Add(ir.OP_uint()) + p.Add(ir.OP_uint(), ir.OP_u) case reflect.Uint8: p.Add(ir.OP_u8) case reflect.Uint16: @@ -301,7 +301,7 @@ func (self *Compiler) compileMapBodyTextKey(p *ir.Program, vk reflect.Type) { case reflect.Bool: p.Key(ir.OP_bool) case reflect.Int: - p.Key(ir.OP_int()) + p.Key(ir.OP_int(), ir.OP_i) case reflect.Int8: p.Key(ir.OP_i8) case reflect.Int16: @@ -311,7 +311,7 @@ func (self *Compiler) compileMapBodyTextKey(p *ir.Program, vk reflect.Type) { case reflect.Int64: p.Key(ir.OP_i64) case reflect.Uint: - p.Key(ir.OP_uint()) + p.Key(ir.OP_uint(), ir.OP_u) case reflect.Uint8: p.Key(ir.OP_u8) case reflect.Uint16: diff --git a/internal/encoder/encoder.go b/internal/encoder/encoder.go index 4cba1a168..f63015ecc 100644 --- a/internal/encoder/encoder.go +++ b/internal/encoder/encoder.go @@ -73,6 +73,13 @@ const ( // Encode Infinity or Nan float into `null`, instead of returning an error. EncodeNullForInfOrNan Options = 1 << alg.BitEncodeNullForInfOrNan + + // Uint64 or Int64 into strings on Marshal + Uint64ToString Options = 1 << alg.BitUint64ToString + Int64ToString Options = 1 << alg.BitInt64ToString + // UintExceedToString when intSize == 64, if the value of type uint or uint64 exceeds MaxInt64, + // automatically convert it to a string. + UintExceedToString Options = 1 << alg.BitUintExceedToString ) // Encoder represents a specific set of encoder configurations. diff --git a/internal/encoder/ir/op.go b/internal/encoder/ir/op.go index a0c693f00..fdc4161db 100644 --- a/internal/encoder/ir/op.go +++ b/internal/encoder/ir/op.go @@ -38,10 +38,12 @@ const ( OP_i16 OP_i32 OP_i64 + OP_i OP_u8 OP_u16 OP_u32 OP_u64 + OP_u OP_f32 OP_f64 OP_str @@ -99,10 +101,12 @@ var OpNames = [256]string{ OP_i16: "i16", OP_i32: "i32", OP_i64: "i64", + OP_i: "i", OP_u8: "u8", OP_u16: "u16", OP_u32: "u32", OP_u64: "u64", + OP_u: "u", OP_f32: "f32", OP_f64: "f64", OP_str: "str", @@ -197,12 +201,26 @@ func OP_is_zero_ints() Op { type Instr struct { o Op + co Op + mapKey bool u int // union {op: 8, _: 8, vi: 48}, vi maybe int or len(str) p unsafe.Pointer // maybe GoString.Ptr, or *GoType } -func NewInsOp(op Op) Instr { - return Instr{o: op} +func NewInsOp(op Op, compatOp ...Op) Instr { + i := Instr{o: op, co: op} + if len(compatOp) == 1 { + i.co = compatOp[0] + } + return i +} + +func NewInsKeyOp(op Op, compatOp ...Op) Instr { + i := Instr{o: op, co: op, mapKey: true} + if len(compatOp) == 1 { + i.co = compatOp[0] + } + return i } func NewInsVi(op Op, vi int) Instr { @@ -255,6 +273,14 @@ func (self Instr) Op() Op { return Op(self.o) } +func (self Instr) CompatOp() Op { + return Op(self.co) +} + +func (self Instr) IsMapKey() bool { + return self.mapKey +} + func (self Instr) Vi() int { return self.u } @@ -410,14 +436,14 @@ func (self Program) Rel(v []int) { } } -func (self *Program) Add(op Op) { - *self = append(*self, NewInsOp(op)) +func (self *Program) Add(op Op, co ...Op) { + *self = append(*self, NewInsOp(op, co...)) } -func (self *Program) Key(op Op) { +func (self *Program) Key(op Op, co ...Op) { *self = append(*self, NewInsVi(OP_byte, '"'), - NewInsOp(op), + NewInsKeyOp(op, co...), NewInsVi(OP_byte, '"'), ) } diff --git a/internal/encoder/vm/vm.go b/internal/encoder/vm/vm.go index b75ba807a..ad4b36dae 100644 --- a/internal/encoder/vm/vm.go +++ b/internal/encoder/vm/vm.go @@ -140,8 +140,17 @@ func Execute(b *[]byte, p unsafe.Pointer, s *vars.Stack, flags uint64, prog *ir. v := *(*int32)(p) buf = alg.I64toa(buf, int64(v)) case ir.OP_i64: + if ins.CompatOp() == ir.OP_i || + ins.IsMapKey() || + flags&(1< uint64(math.MaxInt64) && + flags&(1<