如何利用 enconding/json 的 UnmarshalTypeError 中的偏移值来更好地处理错误?

Ale*_*lex 3 json go

一年多前,GoOffset向类型添加了一个值(请参阅此处json.UnmarshalTypeError已关闭的问题以了解上下文)。偏移值背后的目的是有道理的,但我不确定在读取类型为 的 go http 响应正文时如何使用它。io.ReadCloser

// An UnmarshalTypeError describes a JSON value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError struct {
    Value  string       // description of JSON value - "bool", "array",   "number -5"
    Type   reflect.Type // type of Go value it could not be assigned to
    Offset int64        // error occurred after reading Offset bytes
}
Run Code Online (Sandbox Code Playgroud)

例如:

var body CustomType
decoderErr := json.NewDecoder(response.Body).Decode(&body)

if decoderErr != nil {

    if typeError, ok := decoderErr.(*json.UnmarshalTypeError); ok {
        // Do something with typeError.Offset here
    }


}
Run Code Online (Sandbox Code Playgroud)

在捕获错误时,我已经从response.Bodyvia读取了内容json.NewDecoder...。我正在寻找一种response.Body再次读取的方法,但只能通过使用 typeError 中的 Offset 值来读取错误点。

jma*_*ney 5

由于您想重用请求正文,因此您应该在解组正文之前读取并存储正文,然后如果存在 JSON 语法或类型错误,您可以使用之前存储的正文返回更有用的错误。

概念证明:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

type Hello struct {
    Name    string `json:"name"`
    Message string `json:"message"`
}

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        b, err := ioutil.ReadAll(r.Body)
        if err != nil {
            http.Error(w, "Error reading body", 400)
            return
        }

        h := &Hello{}
        if err := json.Unmarshal(b, &h); err != nil {
            var msg string
            switch t := err.(type) {
            case *json.SyntaxError:
                jsn := string(b[0:t.Offset])
                jsn += "<--(Invalid Character)"
                msg = fmt.Sprintf("Invalid character at offset %v\n %s", t.Offset, jsn)
            case *json.UnmarshalTypeError:
                jsn := string(b[0:t.Offset])
                jsn += "<--(Invalid Type)"
                msg = fmt.Sprintf("Invalid value at offset %v\n %s", t.Offset, jsn)
            default:
                msg = err.Error()
            }
            http.Error(w, msg, 400)
            return
        }

        w.Write([]byte(`Good to go!`))
    })

    if err := http.ListenAndServe(":8000", nil); err != nil {
        log.Fatal(err)
    }
}
Run Code Online (Sandbox Code Playgroud)