package types import ( "errors" "fmt" "strconv" "github.com/shopspring/decimal" ) var errInvalidNumberValue = errors.New("invalid value for Number type") // Number represents a floating point number, and implements json.Unmarshaller and json.Marshaller type Number float64 // UnmarshalJSON implements json.Unmarshaler func (f *Number) UnmarshalJSON(data []byte) error { switch c := data[0]; c { // From json.decode literalInterface case 'n', 't', 'f': // null, true, false return fmt.Errorf("%w: %s", errInvalidNumberValue, data) case '"': // string if len(data) < 2 || data[len(data)-1] != '"' { return fmt.Errorf("%w: %s", errInvalidNumberValue, data) } data = data[1 : len(data)-1] // Naive Unquote default: // Should be a number if c != '-' && (c < '0' || c > '9') { // Invalid json syntax return fmt.Errorf("%w: %s", errInvalidNumberValue, data) } } if len(data) == 0 { *f = Number(0) return nil } val, err := strconv.ParseFloat(string(data), 64) if err != nil { return fmt.Errorf("%w: %s", errInvalidNumberValue, data) // We don't use err; We know it's not valid and errInvalidNumberValue is clearer } *f = Number(val) return nil } // MarshalJSON implements json.Marshaler by formatting to a json string // 1337.37 will marshal to "1337.37" // 0 will marshal to an empty string: "" func (f Number) MarshalJSON() ([]byte, error) { if f == 0 { return []byte(`""`), nil } val := strconv.FormatFloat(float64(f), 'f', -1, 64) return []byte(`"` + val + `"`), nil } // Float64 returns the underlying float64 func (f Number) Float64() float64 { return float64(f) } // Int64 returns the truncated integer component of the number func (f Number) Int64() int64 { // It's likely this is sufficient, since Numbers probably have not had floating point math performed on them // However if issues arise then we can switch to math.Round return int64(f) } // Decimal returns a decimal.Decimal func (f Number) Decimal() decimal.Decimal { return decimal.NewFromFloat(float64(f)) } // String returns a string representation of the number func (f Number) String() string { return strconv.FormatFloat(float64(f), 'f', -1, 64) }