|
| 1 | +package jsonutil |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/json" |
| 5 | + "errors" |
| 6 | + "fmt" |
| 7 | + "io" |
| 8 | +) |
| 9 | + |
| 10 | +// Let's Go Further - Chapter 4.2 |
| 11 | +func Read(r io.Reader, data any) error { |
| 12 | + // Decode the request body into the target destination. |
| 13 | + err := json.NewDecoder(r).Decode(data) |
| 14 | + if err != nil { |
| 15 | + // If there is an error during decoding, start the triage... |
| 16 | + var syntaxError *json.SyntaxError |
| 17 | + var unmarshalTypeError *json.UnmarshalTypeError |
| 18 | + var invalidUnmarshalError *json.InvalidUnmarshalError |
| 19 | + |
| 20 | + switch { |
| 21 | + // Use the errors.As() function to check whether the error has the type |
| 22 | + // *json.SyntaxError. If it does, then return a plain-english error message |
| 23 | + // which includes the location of the problem. |
| 24 | + case errors.As(err, &syntaxError): |
| 25 | + return fmt.Errorf("body contains badly-formed JSON (at character %d)", syntaxError.Offset) |
| 26 | + |
| 27 | + // In some circumstances Decode() may also return an io.ErrUnexpectedEOF error |
| 28 | + // for syntax errors in the JSON. So we check for this using errors.Is() and |
| 29 | + // return a generic error message. There is an open issue regarding this at |
| 30 | + // https://github.com/golang/go/issues/25956. |
| 31 | + case errors.Is(err, io.ErrUnexpectedEOF): |
| 32 | + return errors.New("body contains badly-formed JSON") |
| 33 | + |
| 34 | + // Likewise, catch any *json.UnmarshalTypeError errors. These occur when the |
| 35 | + // JSON value is the wrong type for the target destination. If the error relates |
| 36 | + // to a specific field, then we include that in our error message to make it |
| 37 | + // easier for the client to debug. |
| 38 | + case errors.As(err, &unmarshalTypeError): |
| 39 | + if unmarshalTypeError.Field != "" { |
| 40 | + return fmt.Errorf("body contains incorrect JSON type for field %q", unmarshalTypeError.Field) |
| 41 | + } |
| 42 | + return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshalTypeError.Offset) |
| 43 | + |
| 44 | + // An io.EOF error will be returned by Decode() if the request body is empty. |
| 45 | + // We check for this with errors.Is() and return a plain-english error message |
| 46 | + // instead. |
| 47 | + case errors.Is(err, io.EOF): |
| 48 | + return errors.New("body must not be empty") |
| 49 | + |
| 50 | + // A json.InvalidUnmarshalError error will be returned if we pass something |
| 51 | + // that is not a non-nil pointer to Decode(). We catch this and panic, |
| 52 | + // rather than returning an error to our handler. |
| 53 | + case errors.As(err, &invalidUnmarshalError): |
| 54 | + panic(err) |
| 55 | + |
| 56 | + // For anything else, return the error message as-is. |
| 57 | + default: |
| 58 | + return err |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + return nil |
| 63 | +} |
0 commit comments