Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang Error Types are empty when encoded to JSON

Tags:

json

struct

go

I'm trying to encode some JSON for a REST api, everything is working fine except for some errors. For example, with this struct:

type TemplateResponse struct {
    Message string
    Error   error
    Template Template
}

Encoded with this data:

res := TemplateResponse{"Template not found.", fmt.Errorf("There is no template on this host with the name " + vars["name"]), Template{}}
json.NewEncoder(w).Encode(res)

Returns:

{
  "Message": "Template not found.",
  "Error": {},
  "Template": {
    "Name": "",
    "Disabled": false,
    "Path": "",
    "Version": ""
  }
}

I'm getting this seemingly randomly across my application, where 'error' types are being returned as empty. Any ideas?

Thanks!

like image 720
putitonmytab Avatar asked Jul 08 '17 19:07

putitonmytab


People also ask

How do I encode JSON in Golang?

To encode JSON data we use the Marshal function. Only data structures that can be represented as valid JSON will be encoded: JSON objects only support strings as keys; to encode a Go map type it must be of the form map[string]T (where T is any Go type supported by the json package).

How do I decode JSON in Golang?

Golang provides multiple APIs to work with JSON including to and from built-in and custom data types using the encoding/json package. To parse JSON, we use the Unmarshal() function in package encoding/json to unpack or decode the data from JSON to a struct.

What is Unmarshal JSON?

To unmarshal a JSON array into a slice, Unmarshal resets the slice length to zero and then appends each element to the slice. As a special case, to unmarshal an empty JSON array into a slice, Unmarshal replaces the slice with a new empty slice.

What is Golang Error type?

error is a built-in interface type in Go. An error variable represents any value that can describe itself as a string . The following is the error interface declaration: type error interface { Error() string.


1 Answers

Because error is just an interface. It may hold a value of any concrete type that implements it.

In your example you used fmt.Errorf() to create an error value. That calls errors.New() which returns a pointer to a value of the unexported errors.errorString struct. Its definition is:

type errorString struct {
    s string
}

This struct value will be marshaled, but since it has no exported fields (only exported fields are marshaled), it will be an empty JSON object: {}.

The "fix" is: don't marshal values of "general" interfaces, relying on that the dynamic values can be marshaled into JSON meaningfully. Instead you should add a field that stores the error string (the result of error.Error()), and omit the Error error field from marshaling, e.g.:

type TemplateResponse struct {
    Message  string
    Error    error `json:"-"`
    ErrorMsg string
    Template Template
}

Of course then you also need to set / fill the ErrorMsg field before marshaling.

Or if you don't need to store the error value in the struct, remove that field completely:

type TemplateResponse struct {
    Message  string
    ErrorMsg string
    Template Template
}

If you still want to keep the Error error field (and not the ErrorMsg field), then you need to implement a custom marshaling logic by implementing the json.Marshaler interface where you can "convert" the error value to a meaningful string for example (or into another value that can be marshaled properly).

like image 73
icza Avatar answered Sep 28 '22 04:09

icza