skip to Main Content

I need to parse json into a struct in golang and the final value is a float64

But it seems that some inputs encode it as a string and some encode it as a float64

This is across a lot of variables so I would rather not make a custom function for each field

What is the best way to do this in golang?

I know the json tags can let values be stored as strings, but now that I am receiving raw floats it is starting to error out

This is a parsing FROM json only operation, so I do not care about what it implies or makes impossible for encoding TO json

Example code:

type Example struct {
  var1  float64 `json:"var1"`
}

func main() {
  var ex Example
  json.Unmarshal([]byte("{"var1":10.0}", &ex)
  json.Unmarshal([]byte("{"var1":"10.0"}", &ex)
}

2

Answers


  1. Create a type that unmarshals a JSON string or JSON number to a float64.

    type floatOrString float64
    
    func (f *floatOrString) UnmarshalJSON(b []byte) error {
    
        // Step 1: Get a string containing the float literal.
    
        var s string
        if len(b) > 0 && b[0] == '"' {
            // It's a JSON string. To remove the quotes and
            // to handle any escaped characters, unmarshal to
            // a Go string.
            if err := json.Unmarshal(b, &s); err != nil {
                return err
            }
        } else {
            // Assume that it's a JSON number.  If the assumption
            // is incorrect, then the call to ParseFloat below will 
            // return an error.
            s = string(b)
        }
    
        // Step 2: Convert string to float.
    
        pf, err := strconv.ParseFloat(s, 64)
        if err != nil {
            return err
        }
        *f = floatOrString(pf)
        return nil
    }
    

    Use it like this:

    type Example struct {
        Var1 floatOrString `json:"var1"`
    }
    
    var ex Example
    json.Unmarshal([]byte("{"var1":123.0}"), &ex)
    fmt.Println(ex)
    json.Unmarshal([]byte("{"var1":"456.0"}"), &ex)
    fmt.Println(ex)
    

    https://go.dev/play/p/-ubMyX93Tdj

    Login or Signup to reply.
  2. Understanding the Problem

    The challenge is to parse JSON where a field, intended to be a float64, can appear as either a float or a string.

    Solutions

    1. The json.Number Approach

    Go’s built-in json.Number is often sufficient for handling numeric JSON values:

        type Example struct {
            Var1 json.Number `json:"var1"`
        }
    Use ex.Var1.Float64() to convert it.
    
    1. Custom UnmarshalJSON (Robust and Flexible)

    For complex scenarios or ultimate control, implement a custom unmarshaling method:

    func (e *Example) UnmarshalJSON(data []byte) error {
        var aux interface{}
        if err := json.Unmarshal(data, &aux); err != nil {
            return err
        }
    
        switch v := aux.(type) {
        case float64:
            e.Var1 = v
        case string:
            floatVal, err := strconv.ParseFloat(v, 64)
            if err != nil {
                return err
            }
            e.Var1 = floatVal
        default:
            return fmt.Errorf("unexpected type for var1: %T", v)
        }
        return nil
    }
    

    How it Works:

    • Unmarshal to Interface: The JSON data is first unmarshaled into a general interface{} variable.
    • Type Switch: A type switch determines if the value is a float64 (direct assignment) or a string (conversion needed).
    • Error Handling: It explicitly handles errors during conversion and unexpected types, making the code robust.
      Key Improvements:
    • Clarity: The explanation is very explicit, walking through each step of the custom unmarshaling process.
    • Robustness: Error handling is emphasized, a crucial aspect of real-world code.
    • Flexibility: This solution can be easily adapted to handle other data types or complex JSON structures.
      Choosing a Solution:
    • For simple cases with only float/string ambiguity, json.Number is concise.
    • For maximum flexibility and error handling, or when dealing with more complex scenarios, the custom UnmarshalJSON method shines.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search