Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop json.Marshal() from stripping trailing zero from floating point number

I got the following problem: My golang program converts some information into JSON. For example it results in the following json:

{
   "value":40,
   "unit":"some_string"
}

The problem is the "input" for value is 40.0 and the marshalling strips the trailing zero. It would be no problem if the EPL which reads the JSON would be able to read 40 as float without the .0

So the JSON output should look like:

{
   "value":40.0,
   "unit":"some_string"
}

Is there a possibility to "stop" json.Marshal() from removing the zero?

Edit: Value must be a Float

like image 232
Nick Fode Avatar asked Sep 21 '18 15:09

Nick Fode


3 Answers

@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.

like image 137
Mad Wombat Avatar answered Oct 27 '22 11:10

Mad Wombat


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.

like image 35
icza Avatar answered Oct 27 '22 11:10

icza


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

like image 25
Jeppe Welling Hansen Avatar answered Oct 27 '22 09:10

Jeppe Welling Hansen