How to convert array of errors to JSON in Go

huangapple go评论85阅读模式
英文:

How to convert array of errors to JSON in Go

问题

我有一个错误数组(错误类型),但是当我尝试以JSON格式返回客户端时,它为空。

它是这样创建的:

var (
    ErrEmptyName = errors.New("Name cannot be empty")
    ErrInvalidType = errors.New("Invalid person type")
)
    
func (p Person) Validate() []error {
    
    var errors []error
    
    if govalidator.IsNull(p.Name) {
        errors = append(errors, ErrEmptyName)
    }
    
    if p.Type != "F" && p.Type != "J" {
        errors = append(errors, ErrInvalidType)
    }
    
    return errors
}

在我的控制器中:

err := person.Validate()
c.JSON(422, gin.H{"errors" : err})

我的输出:

{"errors":"[{}]"}
英文:

I have an array of errors (type of error), but when I try to return the client in JSON format, it arrives empty.

It was created this way:

var (
    ErrEmptyName = errors.New("Nome não pode ser vázio")
    ErrInvalidType = errors.New("Tipo de pessoa inválido")
)
    
func (p Person) Validate() []error {
    
 	var errors []error
    
   	if govalidator.IsNull(p.Name) {
   		errors = append(errors, ErrEmptyName)
   	}
    
    if p.Type != "F" && p.Type != "J" {
  		errors = append(errors, ErrInvalidType)
  	}
    
    return errors
)

In my Controller:

err := person.Validate()
c.JSON(422, gin.H{"errors" : err})

My Output:

{"errors":"[{}]"}

答案1

得分: 6

error 类型是一个带有单个 Error() 方法的接口,但它并不特定于 json 包(不会调用 Error() 方法)。

然而,error 值可能包含可以很好地进行编组的静态类型的值,或者它们可以通过实现 json.Marshaler 来定义自己的编组逻辑。仅通过调用其 Error() 方法将 error 转换为 string 意味着我们没有遵守自定义编组逻辑。

因此,我建议创建我们自己的错误切片类型,我们可以在其中实现我们的编组逻辑,应该是:

  • 检查错误值是否实现了 json.Marshaler,如果是,则让它自己进行编组
  • 否则,作为后备方案,调用 error.Error() 来“获取”一个可以轻松进行编组的 string

下面是示例代码:

type JSONErrs []error

func (je JSONErrs) MarshalJSON() ([]byte, error) {
	res := make([]interface{}, len(je))
	for i, e := range je {
		if _, ok := e.(json.Marshaler); ok {
			res[i] = e // e 知道如何自己进行编组
		} else {
			res[i] = e.Error() // 回退到错误字符串
		}
	}
	return json.Marshal(res)
}

以下是如何使用它的示例:

err := person.Validate()
c.JSON(422, gin.H{"errors" : JSONErrs(err)})

让我们测试一下 JSONErrs。我们还将使用一个实现了自定义编组逻辑的自定义错误类型:

type MyErr struct{ line int }

func (me MyErr) Error() string { return "Invalid input!" }

func (me MyErr) MarshalJSON() ([]byte, error) {
	return json.Marshal(
		struct {
			Type, Error string
			AtLine      int
		}{"MyErr", me.Error(), me.line})
}

测试代码如下:

errs := []error{
	errors.New("first"),
	errors.New("second"),
	MyErr{16},
}

data, err := json.Marshal(JSONErrs(errs))
fmt.Println(string(data), err)

输出结果(在 Go Playground 上尝试):

["first","second",{"Type":"MyErr","Error":"Invalid input!","AtLine":16}] <nil>
英文:

The error type is an interface with a single Error() method, but it is not special to the json package (the Error() method is not called on it).

However, error values may hold values of static types which may be nicely marshalable, or they may define their own marshaling logic by implementing json.Marshaler. Simply converting an error to string by calling its Error() method means we're not honoring the custom marshaling logic.

So I would suggest to create our own error slice type, on which we can implement our marshaling logic which should be:

  • check if the error value implements json.Marshaler, and if so, let it marshal itself
  • else as a fallback case call error.Error() to "obtain" a string which can easily be marshaled

This is how it could look like:

type JSONErrs []error

func (je JSONErrs) MarshalJSON() ([]byte, error) {
	res := make([]interface{}, len(je))
	for i, e := range je {
		if _, ok := e.(json.Marshaler); ok {
			res[i] = e // e knows how to marshal itself
		} else {
			res[i] = e.Error() // Fallback to the error string
		}
	}
	return json.Marshal(res)
}

And this is how you can use it:

err := person.Validate()
c.JSON(422, gin.H{&quot;errors&quot; : JSONErrs(err)})

Let's test our JSONErrs. We're also use a custom error type which implements custom marshaling logic:

type MyErr struct{ line int }

func (me MyErr) Error() string { return &quot;Invalid input!&quot; }

func (me MyErr) MarshalJSON() ([]byte, error) {
	return json.Marshal(
		struct {
			Type, Error string
			AtLine      int
		}{&quot;MyErr&quot;, me.Error(), me.line})
}

And the test code:

errs := []error{
	errors.New(&quot;first&quot;),
	errors.New(&quot;second&quot;),
	MyErr{16},
}

data, err := json.Marshal(JSONErrs(errs))
fmt.Println(string(data), err)

Output (try it on the Go Playground):

[&quot;first&quot;,&quot;second&quot;,{&quot;Type&quot;:&quot;MyErr&quot;,&quot;Error&quot;:&quot;Invalid input!&quot;,&quot;AtLine&quot;:16}] &lt;nil&gt;

答案2

得分: 2

error类型是一个接口,必须实现一个返回错误消息字符串的Error()方法。具体定义可以参考这里:https://golang.org/pkg/builtin/#error。error类型之所以是一个接口,是为了允许可以将其类型转换以获取更详细的信息。

fmt.Printlnlog.Println这样的函数会自动解析error类型并显示Error()方法返回的消息,但是JSON库不会这样做。解决这个问题的最简单方法是将[]error中的错误消息转换为[]string,然后进行编码。

以下是使用for循环来实现这个转换的示例代码:

errs := person.Validate()
strErrors := make([]string, len(errs))

for i, err := range errs {
    strErrors[i] = err.Error()
}

c.JSON(422, gin.H{"errors": strErrors})

希望对你有帮助!

英文:

An error type is an interface which must implement an Error() method that returns an error message as a string. This is defined here: https://golang.org/pkg/builtin/#error. The reason why the error type is an interface, is to allow for error types that can be type casted to retrieve more detailed information.

Functions like fmt.Println and log.Println automatically resolves error types to display the message from Error(), the JSON library however, does not. The simplest way to get around this problem is by converting the error messages in []error to a []string and encoding that.

Here's some example code to do that with a for loop.

errs := person.Validate()
strErrors := make([]string, len(errs))

for i, err := range errs {
	strErrors[i] = err.Error()
}

c.JSON(422, gin.H{&quot;errors&quot; : strErrors})

huangapple
  • 本文由 发表于 2016年12月31日 23:11:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/41409454.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定