ZhangZhihui's Blog  

 

 

复制代码
func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Foo string `json:"foo"`
    }
    // Use io.ReadAll() to read the entire request body into a []byte slice.
    body, err := io.ReadAll(r.Body)
    if err != nil {
        app.serverErrorResponse(w, r, err)
        return
    }
    // Use the json.Unmarshal() function to decode the JSON in the []byte slice to the
    // input struct. Again, notice that we are using a *pointer* to the input
    // struct as the decode destination.
    err = json.Unmarshal(body, &input)
    if err != nil {
        app.errorResponse(w, r, http.StatusBadRequest, err.Error())
        return
    }
    fmt.Fprintf(w, "%+v\n", input)
}
复制代码

 

 

Additional JSON decoding nuances

There are a few JSON decoding nuances that are important or interesting to know about, but which don’t fit nicely into the main content of this book. I’ve included this appendix which explains and demonstrates them in detail.

 

 

复制代码
func (app *application) readJSON(r *http.Request, target any) error {
    err := json.NewDecoder(r.Body).Decode(target)
    if err != nil {
        // If there is an error during decoding, start the triage...
        var syntaxError *json.SyntaxError
        var unmarshalTypeError *json.UnmarshalTypeError
        var invalidUnmarshalError *json.InvalidUnmarshalError

        switch {
        // Use the errors.As() function to check whether the error has the type 
        // *json.SyntaxError. If it does, return a plain-english error message which 
        // includes the location of the problem.
        case errors.As(err, &syntaxError):
            return fmt.Errorf("body contains invalid JSON (at character %d)", syntaxError.Offset)

        // In some circumstances Decode() may also return an io.ErrUnexpectedEOF error for syntax 
        // errors in the JSON. So we check for this using errors.Is() and return a generic error 
        // message. There is an open issue regarding this at 
        // https://github.com/golang/go/issues/25956.
        case errors.Is(err, io.ErrUnexpectedEOF):
            return errors.New("body contains invalid JSON")

        // Likewise, catch any *json.UnmarshalTypeError errors. These occur when the JSON value 
        // is the wrong type for the target destination. If the error relates to a specific field, 
        // we include that in our error message to make it easier for the client to debug.
        case errors.As(err, &unmarshalTypeError):
            if unmarshalTypeError.Field != "" {
                return fmt.Errorf("body contains incorrect JSON type for field %s", unmarshalTypeError.Field)
            }
            return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshalTypeError.Offset)

        // An io.EOF error will be returned by Decode() if the request body is empty. We check for 
        // this with errors.Is() and return a plain-english error message instead.
        case errors.Is(err, io.EOF):
            return errors.New("body must not be empty")

        // A json.InvalidUnmarshalError will be returned if we pass something that is not a 
        // non-nil pointer to Decode(). We catch this and panic, rather than returning an error 
        // to our handler. At the end of this chapter we'll talk about panicking versus returning 
        // errors, and discuss why it's an appropriate thing to do in this specific situation.
        case errors.As(err, &invalidUnmarshalError):
            panic(err)

        // For anything else, return the error message as-is.
        default:
            return err
        }
    }

    return nil
}
复制代码

 

 

复制代码
func (app *application) readJSON(w http.ResponseWriter, r *http.Request, dst any) error {
    // Use http.MaxBytesReader() to limit the size of the request body to 1MB.
    maxBytes := 1048576
    r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))

    decoder := json.NewDecoder(r.Body)
    decoder.DisallowUnknownFields()

    err := decoder.Decode(dst)
    if err != nil {
        // If there is an error during decoding, start the triage...
        var syntaxError *json.SyntaxError
        var unmarshalTypeError *json.UnmarshalTypeError
        var invalidUnmarshalError *json.InvalidUnmarshalError
        var maxBytesError *http.MaxBytesError

        switch {
        case errors.As(err, &syntaxError):
            return fmt.Errorf("body contains invalid JSON (at character %d)", syntaxError.Offset)

        case errors.Is(err, io.ErrUnexpectedEOF):
            return errors.New("body contains invalid JSON")

        case errors.As(err, &unmarshalTypeError):
            if unmarshalTypeError.Field != "" {
                return fmt.Errorf("body contains incorrect JSON type for field %s", unmarshalTypeError.Field)
            }
            return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshalTypeError.Offset)

        case errors.Is(err, io.EOF):
            return errors.New("body must not be empty")

        // If the JSON contains a field which cannot be mapped to the target destination then 
        // Decode() will now return an error message in the format "json: unknown field "<name>"". 
        // We check for this, extract the field name from the error, and interpolate it into our 
        // custom error message. Not that there's an open issue at 
        // https://github.com/golang/go/issues/29035 regarding turning this into a distinct error 
        // type in the future.
        case strings.HasPrefix(err.Error(), "json: unknown field "):
            fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
            return fmt.Errorf("body contains unknown key %s", strings.Trim(fieldName, "\""))

        case errors.As(err, &maxBytesError):
            return fmt.Errorf("body must not be larger than %d bytes", maxBytesError.Limit)

        case errors.As(err, &invalidUnmarshalError):
            panic(err)

        default:
            return err
        }
    }

    // Call Decode() again, using a pointer to an empty anonymous struct as the destination. If 
    // the request body only contained a single JSON value this will return an io.EOF error. So 
    // if we get anything else, we know that there is additional data in the rquest body and we 
    // return our own custom error message.
    err = decoder.Decode(&struct{}{})
    if !errors.Is(err, io.EOF) {
        return errors.New("body must only contain a single JSON value")
    }

    return nil
}
复制代码

 

 

复制代码
func (app *application) createMovieHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Title   string       `json:"title"`
        Year    int32        `json:"year"`
        Runtime data.Runtime `json:"runtime"`
        Genres  []string     `json:"genres"`
    }

    err := app.readJSON(w, r, &input)
    if err != nil {
        app.badRequestResponse(w, r, err)
        return
    }

    movie := data.Movie{
        Title:   input.Title,
        Year:    input.Year,
        Runtime: input.Runtime,
        Genres:  input.Genres,
    }

    v := validator.New()

    if data.ValidateMovie(v, &movie); !v.Valid() {
        app.failedValidationResponse(w, r, v.Errors)
        return
    }

    fmt.Fprintf(w, "%+v\n", input)
}
复制代码

 

 

posted on   ZhangZhihuiAAA  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2023-11-15 Microservice - Hexagonal Architecture
 
点击右上角即可分享
微信分享提示