Request Package
This package can be used in order decode the JSON body from incoming api requests and assign the parsed value into a golang struct. The package simplify request body parsing for projects multiple api endpoint handlers. Additionally, this is framework
coherent, using framework specific methods to parse the JSON body.
Code Implementation
package request
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
func DecodeJSON(w http.ResponseWriter, r *http.Request, dst interface{}) error {
return decodeJSON(w, r, dst, false)
}
func DecodeJSONStrict(w http.ResponseWriter, r *http.Request, dst interface{}) error {
return decodeJSON(w, r, dst, true)
}
func decodeJSON(w http.ResponseWriter, r *http.Request, dst interface{}, disallowUnknownFields bool) error {
r.Body = http.MaxBytesReader(w, r.Body, 1_048_576)
dec := json.NewDecoder(r.Body)
if disallowUnknownFields {
dec.DisallowUnknownFields()
}
err := dec.Decode(dst)
if err != nil {
var (
syntaxError *json.SyntaxError
unmarshalTypeError *json.UnmarshalTypeError
invalidUnmarshalError *json.InvalidUnmarshalError
maxBytesError *http.MaxBytesError
)
switch {
case errors.Is(err, io.EOF):
return errors.New("body must not be empty")
case errors.As(err, &syntaxError):
return fmt.Errorf("body contains badly-formed JSON (at character %d)", syntaxError.Offset)
case errors.Is(err, io.ErrUnexpectedEOF):
return errors.New("body contains badly-formed JSON")
case errors.As(err, &unmarshalTypeError):
if unmarshalTypeError.Field != "" {
return fmt.Errorf("body contains incorrect JSON type for field %q", unmarshalTypeError.Field)
}
return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshalTypeError.Offset)
case strings.HasPrefix(err.Error(), "json: unknown field "):
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
return fmt.Errorf("body contains unknown key %s", 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
}
}
err = dec.Decode(&struct{}{})
if !errors.Is(err, io.EOF) {
return errors.New("body must only contain a single JSON value")
}
return nil
}
Usage
Here is small code implementation which shows how the package can be used:
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"<your-project>/internal/request"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
func createUserHandler(w http.ResponseWriter, r *http.Request) {
var user User
if err := request.DecodeJSON(w, r, &user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Process user...
}