I got the following problem: My golang program converts some information into JSON. For example it results in the following json:
{
\"value\":40,
\"uni
@icza provided a good answer, but just to offer another option, you can define your own float type and define your own serialization for it. Like this
type KeepZero float64
func (f KeepZero) MarshalJSON() ([]byte, error) {
if float64(f) == float64(int(f)) {
return []byte(strconv.FormatFloat(float64(f), 'f', 1, 32)), nil
}
return []byte(strconv.FormatFloat(float64(f), 'f', -1, 32)), nil
}
type Pt struct {
Value KeepZero
Unit string
}
func main() {
data, err := json.Marshal(Pt{40.0, "some_string"})
fmt.Println(string(data), err)
}
This results in {"Value":40.0,"Unit":"some_string"} <nil>
. Check it out in playground.
I had a similar issue where I wanted to marshal a map[string]interface{}
with float values f.x 1.0
to JSON as 1.0
. I solved it by adding a custom Marshal function for a custom float type and then replace the floats in the map with the custom type:
type customFloat float64
func (f customFloat) MarshalJSON() ([]byte, error) {
if float64(f) == math.Trunc(float64(f)) {
return []byte(fmt.Sprintf("%.1f", f)), nil
}
return json.Marshal(float64(f))
}
func replaceFloat(value map[string]interface{}) {
for k, v := range value {
switch val := v.(type) {
case map[string]interface{}:
replaceFloat(val)
case float64:
value[k] = customFloat(val)
}
}
}
Then replace all float64 nodes:
replaceFloat(myValue)
bytes, err := json.Marshal(myValue)
This will print the floats like 1.0
Save the value as a string and cast it back if you need it.
By default floating point numbers are rendered without a decimal point and fractions if its value is an integer value. The representation is shorter, and it means the same number.
If you want control over how a number appears in the JSON representation, use the json.Number type.
Example:
type Pt struct {
Value json.Number
Unit string
}
func main() {
data, err := json.Marshal(Pt{json.Number("40.0"), "some_string"})
fmt.Println(string(data), err)
}
Output (try it on the Go Playground):
{"Value":40.0,"Unit":"some_string"} <nil>
If you have a number as a float64
value, you may convert it to json.Number
like this:
func toNumber(f float64) json.Number {
var s string
if f == float64(int64(f)) {
s = fmt.Sprintf("%.1f", f) // 1 decimal if integer
} else {
s = fmt.Sprint(f)
}
return json.Number(s)
}
Testing it:
f := 40.0
data, err := json.Marshal(Pt{toNumber(f), "some_string"})
fmt.Println(string(data), err)
f = 40.123
data, err = json.Marshal(Pt{toNumber(f), "some_string"})
fmt.Println(string(data), err)
Output (try it on the Go Playground):
{"Value":40.0,"Unit":"some_string"} <nil>
{"Value":40.123,"Unit":"some_string"} <nil>
The other direction, if you want the float64
value of a json.Number
, simply call its Number.Float64() method.
type MyFloat float64
func (mf MyFloat) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%.1f", float64(mf))), nil
}
used like this
type PricePoint struct {
Price MyFloat `json:"price"`
From time.Time `json:"valid_from"`
To time.Time `json:"valid_to"`
}
Replace the 1
in "%.1f"
with what ever precision you need