diff --git a/json_column.go b/json_column.go index fd4cf4b..3b28600 100644 --- a/json_column.go +++ b/json_column.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "path" - "strconv" "time" "cloud.google.com/go/civil" @@ -36,13 +35,20 @@ func (c *JSONColumn) marshal(t *gspanner.Type, v *structpb.Value) (interface{}, // See: https://godoc.org/google.golang.org/genproto/googleapis/spanner/v1#TypeCode switch t.Code { case gspanner.TypeCode_INT64: + // JavaScript's integral part is 53bit. see Number.MAX_SAFE_INTEGER. + return v.GetStringValue(), nil + case gspanner.TypeCode_FLOAT64: s := v.GetStringValue() - n, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return nil, err + switch s { + case "NaN", "Infinity", "-Infinity": + // NaN, Infinity, -Infinity will be null on JSON with JavaScript. + // http://www.ecma-international.org/ecma-262/10.0/index.html#sec-serializejsonproperty + return nil, nil } - return n, nil - case gspanner.TypeCode_FLOAT64: + // golang: https://golang.org/ref/spec#Numeric_types + // float64 the set of all IEEE-754 64-bit floating-point numbers + // ECMAScript: http://www.ecma-international.org/ecma-262/10.0/index.html#sec-ecmascript-language-types-number-type + // representing the double-precision 64-bit format IEEE 754-2008 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic return v.GetNumberValue(), nil case gspanner.TypeCode_STRING: return v.GetStringValue(), nil @@ -99,7 +105,10 @@ func (c *JSONColumn) schema(o JSONObject, t *gspanner.Type, options ...jsonschem switch t.Code { default: return fmt.Errorf("unsupport type: type:%v", t) - case gspanner.TypeCode_INT64, gspanner.TypeCode_FLOAT64: + case gspanner.TypeCode_INT64: + o.Set("type", "string") + o.Set("format", "int64") + case gspanner.TypeCode_FLOAT64: o.Set("type", "number") case gspanner.TypeCode_STRING: o.Set("type", "string") diff --git a/json_column_test.go b/json_column_test.go index f5791fa..5035805 100644 --- a/json_column_test.go +++ b/json_column_test.go @@ -5,12 +5,14 @@ import ( "cloud.google.com/go/spanner" . "github.com/gcpug/hake" + structpb "github.com/golang/protobuf/ptypes/struct" + gspanner "google.golang.org/genproto/googleapis/spanner/v1" ) func TestColumn_MarshalJSON(t *testing.T) { type T struct { - N int + N interface{} S string } @@ -18,18 +20,26 @@ func TestColumn_MarshalJSON(t *testing.T) { T T } + floatWithString := func(s string) *spanner.GenericColumnValue { + return &spanner.GenericColumnValue{Type: &gspanner.Type{Code: gspanner.TypeCode_FLOAT64}, Value: &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: s}}} + } + cases := []struct { name string col *spanner.GenericColumnValue want string }{ {"null", column(t, nil), toJSON(t, nil)}, - {"int", column(t, 100), toJSON(t, 100)}, + {"int", column(t, 100), toJSON(t, "100")}, + {"big int", column(t, 72057596404714278), toJSON(t, "72057596404714278")}, {"float", column(t, 10.5), toJSON(t, 10.5)}, + {"float with NaN", floatWithString("NaN"), toJSON(t, nil)}, + {"float with Infinity", floatWithString("Infinity"), toJSON(t, nil)}, + {"float with -Infinity", floatWithString("-Infinity"), toJSON(t, nil)}, {"string", column(t, "test"), toJSON(t, "test")}, {"bool", column(t, true), toJSON(t, true)}, - {"struct", column(t, T{N: 100, S: "test"}), toJSON(t, T{N: 100, S: "test"})}, - {"nested struct", column(t, NT{T{N: 100, S: "test"}}), toJSON(t, NT{T{N: 100, S: "test"}})}, + {"struct", column(t, T{N: 100, S: "test"}), toJSON(t, T{N: "100", S: "test"})}, + {"nested struct", column(t, NT{T{N: 100, S: "test"}}), toJSON(t, NT{T{N: "100", S: "test"}})}, {"timestamp", column(t, timestamp(t, "2002-10-02T10:00:00Z")), toJSON(t, "2002-10-02T10:00:00Z")}, {"bytes", column(t, []byte("test")), toJSON(t, []byte("test"))}, } diff --git a/json_row_test.go b/json_row_test.go index 09ac830..bdf1741 100644 --- a/json_row_test.go +++ b/json_row_test.go @@ -18,8 +18,8 @@ func TestJSONRow_MarshalJSON(t *testing.T) { }{ {"null", row(t, nil), toJSON(t, nil)}, {"empty", row(t, R{}), toJSON(t, R{})}, - {"single", row(t, R{"col1", 100}), toJSON(t, R{"col1", 100})}, - {"multiple", row(t, R{"col1", 100, "col2", 10.5}), toJSON(t, R{"col1", 100, "col2", 10.5})}, + {"single", row(t, R{"col1", 100}), toJSON(t, R{"col1", "100"})}, + {"multiple", row(t, R{"col1", 100, "col2", 10.5}), toJSON(t, R{"col1", "100", "col2", 10.5})}, } for _, tt := range cases { @@ -42,7 +42,7 @@ func TestRows(t *testing.T) { }{ {"null", rows(t, nil), toJSON(t, nil)}, {"empties", rows(t, []R{{}, {}}), toJSON(t, []R{{}, {}})}, - {"singles", rows(t, []R{{"col1", 100}, {"col1", 200}}), toJSON(t, []R{{"col1", 100}, {"col1", 200}})}, + {"singles", rows(t, []R{{"col1", 100}, {"col1", 200}}), toJSON(t, []R{{"col1", "100"}, {"col1", "200"}})}, } for _, tt := range cases {