bigqueryNullFloat64 add +Inf, -Inf and NaN support (#2618)
This is a new PR for closed PR https://github.com/googleapis/google-cloud-go/pull/2574 because you can only allow edits for maintainers on user-forks and NOT organization forks.
diff --git a/bigquery/nulls.go b/bigquery/nulls.go
index bf3cb9d..6ef53d1 100644
--- a/bigquery/nulls.go
+++ b/bigquery/nulls.go
@@ -18,6 +18,7 @@
"bytes"
"encoding/json"
"fmt"
+ "math"
"reflect"
"strconv"
"time"
@@ -25,6 +26,16 @@
"cloud.google.com/go/civil"
)
+var (
+ jsonNull = []byte("null")
+ posInf = []byte(`"+Inf"`)
+ inf = []byte(`"Inf"`)
+ minusInf = []byte(`"-Inf"`)
+ infinity = []byte(`"Infinity"`)
+ minusInfinity = []byte(`"-Infinity"`)
+ nan = []byte(`"NaN"`)
+)
+
// NullInt64 represents a BigQuery INT64 that may be NULL.
type NullInt64 struct {
Int64 int64
@@ -111,7 +122,21 @@
func (n NullInt64) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Int64) }
// MarshalJSON converts the NullFloat64 to JSON.
-func (n NullFloat64) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Float64) }
+func (n NullFloat64) MarshalJSON() (b []byte, err error) {
+ if n.Valid {
+ switch {
+ case math.IsInf(n.Float64, 1):
+ return infinity, nil
+ case math.IsInf(n.Float64, -1):
+ return minusInfinity, nil
+ case math.IsNaN(n.Float64):
+ return nan, nil
+ default:
+ return json.Marshal(n.Float64)
+ }
+ }
+ return jsonNull, nil
+}
// MarshalJSON converts the NullBool to JSON.
func (n NullBool) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Bool) }
@@ -151,8 +176,6 @@
return fmt.Sprint(v)
}
-var jsonNull = []byte("null")
-
func nulljson(valid bool, v interface{}) ([]byte, error) {
if !valid {
return jsonNull, nil
@@ -181,8 +204,19 @@
n.Float64 = 0
if bytes.Equal(b, jsonNull) {
return nil
+ } else if bytes.Equal(b, posInf) || bytes.Equal(b, inf) || bytes.Equal(b, infinity) {
+ n.Float64 = math.Inf(1)
+ n.Valid = true
+ return nil
+ } else if bytes.Equal(b, minusInf) || bytes.Equal(b, minusInfinity) {
+ n.Float64 = math.Inf(-1)
+ n.Valid = true
+ return nil
+ } else if bytes.Equal(b, nan) {
+ n.Float64 = math.NaN()
+ n.Valid = true
+ return nil
}
-
if err := json.Unmarshal(b, &n.Float64); err != nil {
return err
}
diff --git a/bigquery/nulls_test.go b/bigquery/nulls_test.go
index 92a0b9c..7e5c939 100644
--- a/bigquery/nulls_test.go
+++ b/bigquery/nulls_test.go
@@ -16,6 +16,7 @@
import (
"encoding/json"
+ "math"
"reflect"
"testing"
@@ -52,6 +53,10 @@
{&NullDate{}, `null`},
{&NullTime{}, `null`},
{&NullDateTime{}, `null`},
+
+ {&NullFloat64{Valid: true, Float64: math.Inf(1)}, `"Infinity"`},
+ {&NullFloat64{Valid: true, Float64: math.Inf(-1)}, `"-Infinity"`},
+ {&NullFloat64{Valid: true, Float64: math.NaN()}, `"NaN"`},
} {
bytes, err := json.Marshal(test.in)
if err != nil {
@@ -73,3 +78,83 @@
}
}
}
+
+func TestNullFloat64JSON(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ in string
+ unmarshalled NullFloat64
+ marshalled string
+ }{
+ {
+ name: "float value",
+ in: "3.14",
+ unmarshalled: NullFloat64{Valid: true, Float64: 3.14},
+ marshalled: "3.14",
+ },
+ {
+ name: "null",
+ in: "null",
+ unmarshalled: NullFloat64{},
+ marshalled: "null",
+ },
+ {
+ name: "long infinity",
+ in: `"Infinity"`,
+ unmarshalled: NullFloat64{Valid: true, Float64: math.Inf(1)},
+ marshalled: `"Infinity"`,
+ },
+ {
+ name: "short infinity",
+ in: `"Inf"`,
+ unmarshalled: NullFloat64{Valid: true, Float64: math.Inf(1)},
+ marshalled: `"Infinity"`,
+ },
+ {
+ name: "positive short infinity",
+ in: `"+Inf"`,
+ unmarshalled: NullFloat64{Valid: true, Float64: math.Inf(1)},
+ marshalled: `"Infinity"`,
+ },
+ {
+ name: "minus infinity",
+ in: `"-Infinity"`,
+ unmarshalled: NullFloat64{Valid: true, Float64: math.Inf(-1)},
+ marshalled: `"-Infinity"`,
+ },
+ {
+ name: "minus short infinity",
+ in: `"-Inf"`,
+ unmarshalled: NullFloat64{Valid: true, Float64: math.Inf(-1)},
+ marshalled: `"-Infinity"`,
+ },
+ {
+ name: "NaN",
+ in: `"NaN"`,
+ unmarshalled: NullFloat64{Valid: true, Float64: math.NaN()},
+ marshalled: `"NaN"`,
+ },
+ } {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ var f NullFloat64
+ err := json.Unmarshal([]byte(tc.in), &f)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := f, tc.unmarshalled; !testutil.Equal(got, want) {
+ t.Errorf("%#v: got %#v, want %#v", tc.in, got, want)
+ }
+
+ b, err := json.Marshal(f)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := string(b), tc.marshalled; got != want {
+ t.Errorf("%#v: got %s, want %s", tc.in, got, want)
+ }
+ })
+ }
+}