douweng7083
douweng7083
2019-06-30 08:13
浏览 106
已采纳

如何在Golang rest中输出json格式错误,特别是引用坏字段

I have the following requirement: return errors from a REST API in the following format:

Error format
422
{
    "name-of-field": [
        "can't be blank",
        "is too silly"
    ]
}

My code looks like this:

var PostFeedback = func(w http.ResponseWriter, r *http.Request) {
    params := mux.Vars(r)
    surveyId := params["id"]
    feedback := &models.Feedback{}
    err := json.NewDecoder(r.Body).Decode(feedback)
    if err != nil {
        jsonError := fmt.Sprintf(`{
            "%s": [
                "%s"
            ]
        }`, "errors", err)
        log.Printf("invalid input format, %v", jsonError)
        resp := map[string]interface{}{"error": jsonError}
        u.Respond(w, resp)
        return
    }

Questions:

How do I get the names of the offending fields?
How do I satisfy the requirement best?

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • douzi1350
    douzi1350 2019-06-30 12:57
    已采纳

    The encoding/json package doesn't provide validation for "blank", nor "silly" values. It will return an error only if the data in the body is not a valid json, or if the field types in the json do not, according to the package's spec, match the field types of the structure into which you're trying to decode that json.

    The 1st type of error would be the json.SyntaxError, if you get this it is not always possible to satisfy your requirements since there may be no actual fields which you could use in your response, or if there are json fields, they, and their values, may be perfectly valid json, but the cause of the error may lie elsewhere (see example).

    In cases where the data holds actual json fields but it has, for example, non-json values you could use the Offset field of the SyntaxError type to find the closest preceding field in the data stream. Using strings.LastIndex you can implement a naive solution to look backwards for the field.

    data := []byte(`{"foobar": i'm not json}`)
    
    err := json.Unmarshal(data, &T{})
    se, ok := err.(*json.SyntaxError)
    if !ok {
        panic(err)
    }
    
    field := string(data[:se.Offset])
    if i := strings.LastIndex(field, `":`); i >= 0 {
        field = field[:i]
        if j := strings.LastIndex(field, `"`); j >= 0 {
            field = field[j+1:]
        }
    }
    fmt.Println(field) // outputs foobar
    

    Playground link

    NOTE: As you can see, for you to be able to look for the field, you need to have access to the data, but when you're using json.NewDecoder and passing it the request's body directly, without first storing its contents somewhere, you'll loose access to that data once the decoder's Decode method is done. This is because the body is a stream of bytes wrapped in a io.ReadCloser that does not support "rewinding", i.e. you cannot re-read bytes that the decoder already read. To avoid this you can use ioutil.ReadAll to read the full contents of the body and then json.Unmarshal to do the decoding.


    The 2nd type of error would be the json.UnmarshalTypeError. If you look at the documentation of the error type and its fields you'll know that all you need to do is to type assert the returned value and you're done. Example


    Validation against "blank" and "silly" values would be done after the json has been successfully decoded into your structure. How you do that is up to you. For example you could use a 3rd party package that's designed for validating structs, or you can implement an in-house solution yourself, etc. I actually don't have an opinion on which one of them is the "best" so I can't help you with that.

    What I can say is that the most basic approach would be to simply look at each field of the structure and check if its value is valid or not according to the requirements for that field.

    点赞 评论

相关推荐