我正在将HTTP API中的JSON数据解组为以下Golang结构:
type ResponseBody struct {
Version string `json:"jsonrpc"`
Result Result `json:"result"`
Error Error `json:"error"`
Id int `json:"id"`
}
type Result struct {
Random struct {
Data interface{} `json:"data"`
CompletionTime string `json:"completionTime"`
} `json:"random"`
BitsUsed int `json:"bitsUsed"`
BitsLeft int `json:"bitsLeft"`
RequestsLeft int `json:"requestsLeft"`
AdvisoryDelay int `json:"advisoryDelay"`
}
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Data []int `json:"data,omitempty"`
}
我已经为Error
实现了错误接口,如下所示:
func (e Error) Error() string {
return fmt.Sprintf("Error: %+v", e)
}
到目前为止的相关代码:
func Request(method string, params interface{}) (Result, error) {
// `error` in return types is _not_ a typo
body, err := json.Marshal(RequestBody{Version: "2.0", Params: params, Method: method, Id: 1})
if err != nil {
return Result{}, err
}
resp, err := http.Post(endpoint, "application/json-rpc", bytes.NewReader(body))
if err != nil {
return Result{}, fmt.Errorf("Request failed, error was %s", err)
}
defer resp.Body.Close()
text, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Result{}, fmt.Errorf("Failed to read response into memory, error was: %s", err)
}
if resp.StatusCode != 200 {
var errorMessage Error
if err := json.Unmarshal(text, &errorMessage); err != nil {
return Result{}, Error{
Code: 409,
Message: fmt.Sprintf("Client could not decode JSON error response, received %s, error was: %s", text, err),
Data: []int{},
}
}
return Result{}, errorMessage
}
response := ResponseBody{}
if err := json.Unmarshal(text, &response); err != nil {
return Result{}, fmt.Errorf("Failed to JSON-decode response body, received %s from source, error was: %s", text, err)
}
return response.Result, response.Error
}
以下代码无限期挂起成功通话而不会发生恐慌:
// `body` here is just any old struct
result, err := Request("generateIntegers", body)
if err != nil {
return []int{}, err // hangs here
}
事实上,当我调用err
时代码总是挂起。没有引起恐慌并且没有返回错误 - 它只是被冻结[1]。
[1]严格来说,它会导致堆栈溢出错误,但那是因为函数永远不会返回,因此resp.Body.Close()
中的延迟Request
永远不会被调用。
它变得更奇怪了。添加以下调试行:
response := ResponseBody{}
if err := json.Unmarshal(text, &response); err != nil {
return Result{}, fmt.Errorf("Failed to JSON-decode response body, received %s from source, error was: %s", text, err)
}
fmt.Println(response.Result.BitsUsed)
fmt.Println(response.Result) // this prints!
return response.Result, response.Error
工作,但改变这些行只是
response := ResponseBody{}
if err := json.Unmarshal(text, &response); err != nil {
return Result{}, fmt.Errorf("Failed to JSON-decode response body, received %s from source, error was: %s", text, err)
}
fmt.Println(response.Result) // this no longer prints! :O
return response.Result, response.Error
导致Request
函数本身挂起此调试语句。
go test -trace
运行跟踪以查看所调用的内容。这失败是因为go tool trace
无法解析生成的跟踪文件。*error
而不是常规的error
。这没有用。为什么这段代码片段挂起了?
注意:我正在运行Go 1.7
问题在于Error
界面的定义。当你尝试Error() string
值Error
时,fmt.Sprintf
类型的函数e
递归调用自身:
func (e Error) Error() string {
return fmt.Sprintf("Error: %+v", e) // calls e.Error again
}
尝试通过显式访问qazxsw poi类型成员来返回错误:
Error
问题是递归,因为func (e Error) Error() string {
return fmt.Sprintf("Error: %+v", e.Message)
}
称fmt
再次调用fmt。
经典解决方案是将您的错误类型转换为Error()
,然后使用标准机制。这样您就拥有了所有格式化工具。喜欢:
string
游乐场:type E string
func (e E) Error() string {
return fmt.Sprintf(string(e))
}
来自https://play.golang.org/p/NI5JL3H4g7Y fmt
的摘录(不幸的是不能直接链接到该段):
- 如果操作数实现了错误接口,则将调用Error方法将对象转换为字符串,然后根据动词的需要对其进行格式化(如果有)。
- 如果操作数实现方法String()字符串,则将调用该方法将对象转换为字符串,然后根据动词(如果有)格式化该字符串。
避免在诸如此类的情况下递归
documentation在重复之前转换值:
type X string func (x X) String() string { return Sprintf("<%s>", x) }
无限递归也可以由自引用数据结构触发,例如,如果该类型具有String方法,则将自身包含为元素。然而,这种病症是罕见的,并且包装不能防止它们。
打印结构时,fmt不能,因此不会在未导出的字段上调用错误或字符串等格式化方法。