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) }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2023-11-15 Microservice - Hexagonal Architecture