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

5

8

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

Polygamy answered 21/9, 2018 at 15:16 Comment(0)
S
13

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

Southerly answered 21/9, 2018 at 15:41 Comment(0)
M
10

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.

Memorabilia answered 21/9, 2018 at 15:25 Comment(2)
Is there a way to do this when the JSON structure is only known at runtime, i.e when marshalling to map[string]interface{}?Jenaejenda
@Jenaejenda You can "swap" float64 values in the map with "pre-rendered" json.Number values, and then marshal the map.Memorabilia
C
1

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

Cuspid answered 25/10, 2018 at 19:38 Comment(1)
This is great for many cases. Unfortunately it does not handle a nested array-of-floats. They will not be converted to special float typeAloisius
O
0
  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

Offen answered 9/2, 2020 at 7:14 Comment(0)
T
-2

Save the value as a string and cast it back if you need it.

Tanbark answered 21/9, 2018 at 15:18 Comment(2)
The idea is not bad, forgot to mention: the json is posted to a platform that does not allow strings as valuePolygamy
Updated my original postPolygamy

© 2022 - 2024 — McMap. All rights reserved.