```golang c.IndentedJSON(http.StatusOK, result) ``` error: json: unsupported value: +Inf How to fix it

2023-10-23by@tim&@ChatGPT.Model: gpt-4
tim
c.IndentedJSON(http.StatusOK, result)

error: json: unsupported value: +Inf

How to fix it

Avatar
Avatar

The error message is saying that you're trying to serialize a value of +Infinity to JSON, which is not supported by the encoding/json package in Go.

You can handle this error by checking and replacing the +Inf value before serializing it to JSON.

Here is an example of how you might do it:

for i, val := range result {
    if val == math.Inf(1) {
        result[i] = math.MaxFloat64
    } else if val == math.Inf(-1) {
        result[i] = -math.MaxFloat64
    }
}
c.IndentedJSON(http.StatusOK, result)

In this example, we're replacing positive infinity with the maximum representable float64 value and negative infinity with the minimum representable float64 value. You might want to adjust this depending on your use case.

If your result is not a slice, but a struct, you will need to check and replace the +Inf value in each field of the struct. If the struct is complex, you might want to write a separate function to do this.

Please note that this solution is a workaround. The correct solution would depend on your specific use case. If possible, you should avoid having +Inf values in your data.

tim

is there a go module for this

Avatar
Avatar

There is no specific Go module for handling +Inf in JSON encoding because it's not often that such a case arises. However, you can handle this issue in your code by creating a custom JSON Marshaller.

Here's an example on how to create a custom JSON Marshaller for a struct:

type MyStruct struct {
    Value float64
}

func (m MyStruct) MarshalJSON() ([]byte, error) {
    if math.IsInf(m.Value, 1) {
        m.Value = math.MaxFloat64
    } else if math.IsInf(m.Value, -1) {
        m.Value = -math.MaxFloat64
    }
    type Alias MyStruct
    return json.Marshal(&struct {
        *Alias
    }{
        Alias: (*Alias)(&m),
    })
}

In this code, we're creating a custom MarshalJSON method for MyStruct. Whenever json.Marshal() is called with a MyStruct object, this method will be used to convert the object to JSON. We're checking if Value is +Inf or -Inf, and replacing it with the max or min float64 value if it is.

This way, you can handle +Inf values in your data and prevent them from causing errors when converting the data to JSON.

tim

result is in this struct: type Result struct {
Data interface{} json:"data"
Errors []gqlerrors.FormattedError json:"errors,omitempty"
Extensions map[string]interface{} json:"extensions,omitempty"
}

Avatar
Avatar

The structure of the Result struct makes it a bit complex to handle +Inf values because the Data field is of type interface{}. This means that it can hold any type, and we don't know beforehand which fields to check for +Inf values.

One way to handle this would be to convert Data to a map, iterate over it, and replace any +Inf values. But this would only work if the data is a single level deep. If you have nested fields, you would need to recursively check and replace +Inf values.

A more robust and general solution would be to use a custom JSON Marshaller that can handle +Inf values. This would involve replacing the encoding/json package with a package that supports +Inf values.

One such package is jsoniter, which is a drop-in replacement for encoding/json. It's more flexible and also faster than encoding/json.

Here's how to use it:

First, install the package:

go get github.com/json-iterator/go

Then, in your code:

import (
    "github.com/gin-gonic/gin"
    jsoniter "github.com/json-iterator/go"
)

func someFunction(c *gin.Context) {
    var json = jsoniter.ConfigCompatibleWithStandardLibrary
    // ... 
    // result is of type `Result`
    jsonBytes, err := json.Marshal(result)
    if err != nil {
        // Handle error
    }
    c.Data(http.StatusOK, "application/json; charset=utf-8", jsonBytes)
}

In this code, instead of using c.IndentedJSON(), we're first converting the result to JSON using jsoniter, and then sending it as the response.

This way, +Inf values will be converted to Infinity in the resulting JSON, which is valid JSON. Note that not all JSON parsers can handle this value, so you should check if this solution works in your case.

tim

This is my current code:

	result := graphql.Do(params)
	if len(result.Errors) > 0 {
		WriteErrorResponse(c, http.StatusInternalServerError, "Failed process graphql query", graphqlbiz.GetError(result))
		return
	}

	c.IndentedJSON(http.StatusOK, result)
Avatar
Avatar

Given your current code, you can replace the c.IndentedJSON(http.StatusOK, result) line with the jsoniter code I provided earlier. Here is how you can modify your code:

import (
    "github.com/gin-gonic/gin"
    jsoniter "github.com/json-iterator/go"
)

// ...

func someFunction(c *gin.Context) {
    result := graphql.Do(params)
    if len(result.Errors) > 0 {
        WriteErrorResponse(c, http.StatusInternalServerError, "Failed process graphql query", graphqlbiz.GetError(result))
        return
    }

    var json = jsoniter.ConfigCompatibleWithStandardLibrary
    jsonBytes, err := json.Marshal(result)
    if err != nil {
        // Handle the error the way you want, for example:
        WriteErrorResponse(c, http.StatusInternalServerError, "Failed to process JSON", err)
        return
    }
    c.Data(http.StatusOK, "application/json; charset=utf-8", jsonBytes)
}

In this code, instead of using c.IndentedJSON(), we're first converting the result to JSON using jsoniter, and then sending it as the response with c.Data(). With jsoniter, +Inf values will be converted to Infinity in the resulting JSON, which is a valid JSON value.

tim

I see this line in your code:
// Handle the error the way you want, for example:

what kind of err might happen

Avatar
Avatar

The error from the json.Marshal function can occur if the input to the function cannot be encoded to JSON.

This could happen for several reasons:

  • If the input includes unsupported types, such as functions or channels.
  • If the input includes unsupported values, such as NaN or Infinity (although jsoniter should be able to handle Infinity).
  • If the input is a struct with unexported fields, because unexported fields cannot be accessed from other packages and thus cannot be encoded.
  • If the input includes circular references, because JSON does not support circular references.

In your specific case, where you're encoding a GraphQL result, the most likely cause of an error would be if the GraphQL result includes a value of a type that cannot be encoded to JSON.

However, errors from json.Marshal are relatively rare, and it's generally safe to assume that if your data can be represented as JSON, the function will not return an error. It's still good practice to handle the error, just in case something unexpected happens.