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

后端 未结 5 480
梦谈多话
梦谈多话 2020-12-21 01:04

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

{
   \"value\":40,
   \"uni         


        
相关标签:
5条回答
  • 2020-12-21 01:11

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

    0 讨论(0)
  • 2020-12-21 01:12

    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

    0 讨论(0)
  • 2020-12-21 01:21

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

    0 讨论(0)
  • 2020-12-21 01:31

    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.

    0 讨论(0)
  • 2020-12-21 01:35
      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

    0 讨论(0)
提交回复
热议问题