Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add native json.Number support #364

Merged
merged 4 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions _generated/def.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package _generated

import (
"encoding/json"
"os"
"time"

Expand Down Expand Up @@ -299,3 +300,10 @@ type StructByteSlice struct {
AComplex128 []complex128 `msg:",allownil"`
AStruct []Fixed `msg:",allownil"`
}

type NumberJSONSample struct {
Single json.Number
Array []json.Number
Map map[string]json.Number
OE json.Number `msg:",omitempty"`
}
105 changes: 105 additions & 0 deletions _generated/def_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package _generated

import (
"bytes"
"encoding/json"
"reflect"
"testing"

Expand Down Expand Up @@ -74,3 +75,107 @@ func TestRuneMarshalUnmarshal(t *testing.T) {
t.Errorf("rune slice mismatch")
}
}

func TestJSONNumber(t *testing.T) {
test := NumberJSONSample{
Single: "-42",
Array: []json.Number{"0", "-0", "1", "-1", "0.1", "-0.1", "1234", "-1234", "12.34", "-12.34", "12E0", "12E1", "12e34", "12E-0", "12e+1", "12e-34", "-12E0", "-12E1", "-12e34", "-12E-0", "-12e+1", "-12e-34", "1.2E0", "1.2E1", "1.2e34", "1.2E-0", "1.2e+1", "1.2e-34", "-1.2E0", "-1.2E1", "-1.2e34", "-1.2E-0", "-1.2e+1", "-1.2e-34", "0E0", "0E1", "0e34", "0E-0", "0e+1", "0e-34", "-0E0", "-0E1", "-0e34", "-0E-0", "-0e+1", "-0e-34"},
Map: map[string]json.Number{
"a": json.Number("50.2"),
},
}

// This is not guaranteed to be symmetric
encoded, err := test.MarshalMsg(nil)
if err != nil {
t.Errorf("%v", err)
}
var v NumberJSONSample
_, err = v.UnmarshalMsg(encoded)
if err != nil {
t.Errorf("%v", err)
}
// Test two values
if v.Single != "-42" {
t.Errorf("want %v, got %v", "-42", v.Single)
}
if v.Map["a"] != "50.2" {
t.Errorf("want %v, got %v", "50.2", v.Map["a"])
}

var jsBuf bytes.Buffer
remain, err := msgp.UnmarshalAsJSON(&jsBuf, encoded)
if err != nil {
t.Errorf("%v", err)
}
if len(remain) != 0 {
t.Errorf("remain should be empty")
}
wantjs := `{"Single":-42,"Array":[0,0,1,-1,0.1,-0.1,1234,-1234,12.34,-12.34,12,120,120000000000000000000000000000000000,12,120,0.0000000000000000000000000000000012,-12,-120,-120000000000000000000000000000000000,-12,-120,-0.0000000000000000000000000000000012,1.2,12,12000000000000000000000000000000000,1.2,12,0.00000000000000000000000000000000012,-1.2,-12,-12000000000000000000000000000000000,-1.2,-12,-0.00000000000000000000000000000000012,0,0,0,0,0,0,-0,-0,-0,-0,-0,-0],"Map":{"a":50.2}}`
if jsBuf.String() != wantjs {
t.Errorf("jsBuf.String() = \n%s, want \n%s", jsBuf.String(), wantjs)
}
// Test encoding
var buf bytes.Buffer
en := msgp.NewWriter(&buf)
err = test.EncodeMsg(en)
if err != nil {
t.Errorf("%v", err)
}
en.Flush()
encoded = buf.Bytes()

dc := msgp.NewReader(&buf)
err = v.DecodeMsg(dc)
if err != nil {
t.Errorf("%v", err)
}
// Test two values
if v.Single != "-42" {
t.Errorf("want %s, got %s", "-42", v.Single)
}
if v.Map["a"] != "50.2" {
t.Errorf("want %s, got %s", "50.2", v.Map["a"])
}

jsBuf.Reset()
remain, err = msgp.UnmarshalAsJSON(&jsBuf, encoded)
if err != nil {
t.Errorf("%v", err)
}
if len(remain) != 0 {
t.Errorf("remain should be empty")
}
if jsBuf.String() != wantjs {
t.Errorf("jsBuf.String() = \n%s, want \n%s", jsBuf.String(), wantjs)
}

// Try interface encoder
jd := json.NewDecoder(&jsBuf)
jd.UseNumber()
var jsIntf map[string]any
err = jd.Decode(&jsIntf)
if err != nil {
t.Errorf("%v", err)
}
// Ensure we encode correctly
_ = (jsIntf["Single"]).(json.Number)

fromInt, err := msgp.AppendIntf(nil, jsIntf)
if err != nil {
t.Errorf("%v", err)
}

// Take the value from the JSON interface encoder and unmarshal back into our struct.
v = NumberJSONSample{}
_, err = v.UnmarshalMsg(fromInt)
if err != nil {
t.Errorf("%v", err)
}
if v.Single != "-42" {
t.Errorf("want %s, got %s", "-42", v.Single)
}
if v.Map["a"] != "50.2" {
t.Errorf("want %s, got %s", "50.2", v.Map["a"])
}
}
10 changes: 10 additions & 0 deletions _generated/replace.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package _generated

import "encoding/json"

//go:generate msgp
//msgp:replace Any with:any
//msgp:replace MapString with:CompatibleMapString
Expand Down Expand Up @@ -74,3 +76,11 @@ type (
String String
}
)

//msgp:replace json.Number with:string

type NumberJSONSampleReplace struct {
Single json.Number
Array []json.Number
Map map[string]json.Number
}
73 changes: 73 additions & 0 deletions _generated/replace_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package _generated

import (
"bytes"
"encoding/json"
"reflect"
"testing"
"time"

"github.com/tinylib/msgp/msgp"
)

func compareStructD(t *testing.T, a, b *CompatibleStructD) {
Expand Down Expand Up @@ -288,3 +293,71 @@ func TestReplace_Dummy(t *testing.T) {
t.Fatal("not same string")
}
}

func TestJSONNumberReplace(t *testing.T) {
test := NumberJSONSampleReplace{
Single: "-42",
Array: []json.Number{"0", "-0", "1", "-1", "0.1", "-0.1", "1234", "-1234", "12.34", "-12.34", "12E0", "12E1", "12e34", "12E-0", "12e+1", "12e-34", "-12E0", "-12E1", "-12e34", "-12E-0", "-12e+1", "-12e-34", "1.2E0", "1.2E1", "1.2e34", "1.2E-0", "1.2e+1", "1.2e-34", "-1.2E0", "-1.2E1", "-1.2e34", "-1.2E-0", "-1.2e+1", "-1.2e-34", "0E0", "0E1", "0e34", "0E-0", "0e+1", "0e-34", "-0E0", "-0E1", "-0e34", "-0E-0", "-0e+1", "-0e-34"},
Map: map[string]json.Number{
"a": json.Number("50"),
},
}

encoded, err := test.MarshalMsg(nil)
if err != nil {
t.Errorf("%v", err)
}
var v NumberJSONSampleReplace
_, err = v.UnmarshalMsg(encoded)
if err != nil {
t.Errorf("%v", err)
}
// Symmetric since we store strings.
if !reflect.DeepEqual(v, test) {
t.Fatalf("want %v, got %v", test, v)
}

var jsBuf bytes.Buffer
remain, err := msgp.UnmarshalAsJSON(&jsBuf, encoded)
if err != nil {
t.Errorf("%v", err)
}
if len(remain) != 0 {
t.Errorf("remain should be empty")
}
// Retains number formatting. Map order is random, though.
wantjs := `{"Single":"-42","Array":["0","-0","1","-1","0.1","-0.1","1234","-1234","12.34","-12.34","12E0","12E1","12e34","12E-0","12e+1","12e-34","-12E0","-12E1","-12e34","-12E-0","-12e+1","-12e-34","1.2E0","1.2E1","1.2e34","1.2E-0","1.2e+1","1.2e-34","-1.2E0","-1.2E1","-1.2e34","-1.2E-0","-1.2e+1","-1.2e-34","0E0","0E1","0e34","0E-0","0e+1","0e-34","-0E0","-0E1","-0e34","-0E-0","-0e+1","-0e-34"],"Map":{"a":"50"}}`
if jsBuf.String() != wantjs {
t.Errorf("jsBuf.String() = \n%s, want \n%s", jsBuf.String(), wantjs)
}
// Test encoding
var buf bytes.Buffer
en := msgp.NewWriter(&buf)
err = test.EncodeMsg(en)
if err != nil {
t.Errorf("%v", err)
}
en.Flush()
encoded = buf.Bytes()

dc := msgp.NewReader(&buf)
err = v.DecodeMsg(dc)
if err != nil {
t.Errorf("%v", err)
}
if !reflect.DeepEqual(v, test) {
t.Fatalf("want %v, got %v", test, v)
}

jsBuf.Reset()
remain, err = msgp.UnmarshalAsJSON(&jsBuf, encoded)
if err != nil {
t.Errorf("%v", err)
}
if len(remain) != 0 {
t.Errorf("remain should be empty")
}
if jsBuf.String() != wantjs {
t.Errorf("jsBuf.String() = \n%s, want \n%s", jsBuf.String(), wantjs)
}
}
20 changes: 15 additions & 5 deletions gen/elem.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,11 @@ const (
Int32
Int64
Bool
Intf // interface{}
Time // time.Time
Duration // time.Duration
Ext // extension
Intf // interface{}
Time // time.Time
Duration // time.Duration
Ext // extension
JsonNumber // json.Number

IDENT // IDENT means an unrecognized identifier
)
Expand Down Expand Up @@ -123,6 +124,7 @@ var primitives = map[string]Primitive{
"time.Time": Time,
"time.Duration": Duration,
"msgp.Extension": Ext,
"json.Number": JsonNumber,
}

// types built into the library
Expand Down Expand Up @@ -634,6 +636,9 @@ func (s *BaseElem) BaseName() string {
if s.Value == Duration {
return "Duration"
}
if s.Value == JsonNumber {
return "JSONNumber"
}
return s.Value.String()
}

Expand All @@ -652,6 +657,8 @@ func (s *BaseElem) BaseType() string {
return "time.Time"
case Duration:
return "time.Duration"
case JsonNumber:
return "json.Number"
case Ext:
return "msgp.Extension"

Expand Down Expand Up @@ -719,9 +726,10 @@ func (s *BaseElem) ZeroExpr() string {
return "0"
case Bool:
return "false"

case Time:
return "(time.Time{})"
case JsonNumber:
return `""`

}

Expand Down Expand Up @@ -783,6 +791,8 @@ func (k Primitive) String() string {
return "time.Duration"
case Ext:
return "Extension"
case JsonNumber:
return "json.Number"
case IDENT:
return "Ident"
default:
Expand Down
2 changes: 1 addition & 1 deletion gen/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ func (m *marshalGen) gBase(b *BaseElem) {
case IDENT:
echeck = true
m.p.printf("\no, err = %s.MarshalMsg(o)", vname)
case Intf, Ext:
case Intf, Ext, JsonNumber:
echeck = true
m.p.printf("\no, err = msgp.Append%s(o, %s)", b.BaseName(), vname)
default:
Expand Down
34 changes: 34 additions & 0 deletions msgp/read.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package msgp

import (
"encoding/json"
"io"
"math"
"strconv"
"sync"
"time"

Expand Down Expand Up @@ -45,6 +47,7 @@ const (
Complex64Type
Complex128Type
TimeType
NumberType

_maxtype
)
Expand Down Expand Up @@ -74,6 +77,8 @@ func (t Type) String() string {
return "ext"
case NilType:
return "nil"
case NumberType:
return "number"
default:
return "<invalid>"
}
Expand Down Expand Up @@ -1276,6 +1281,35 @@ func (m *Reader) ReadTime() (t time.Time, err error) {
return
}

// ReadJSONNumber reads an integer or a float value and return as json.Number
func (m *Reader) ReadJSONNumber() (n json.Number, err error) {
t, err := m.NextType()
if err != nil {
return
}
switch t {
case IntType:
v, err := m.ReadInt64()
if err == nil {
return json.Number(strconv.FormatInt(v, 10)), nil
}
return "", err
case UintType:
v, err := m.ReadUint64()
if err == nil {
return json.Number(strconv.FormatUint(v, 10)), nil
}
return "", err
case Float32Type, Float64Type:
v, err := m.ReadFloat64()
if err == nil {
return json.Number(strconv.FormatFloat(v, 'f', -1, 64)), nil
}
return "", err
}
return "", TypeError{Method: NumberType, Encoded: t}
}

// ReadIntf reads out the next object as a raw interface{}/any.
// Arrays are decoded as []interface{}, and maps are decoded
// as map[string]interface{}. Integers are decoded as int64
Expand Down
Loading
Loading