Golang中如何为API响应定义通用的成功/错误结果结构
Golang中如何为API响应定义通用的成功/错误结果结构 我来自 TypeScript 背景,想要为我的 API 响应定义对象。在 TS 中我使用了:
type Result<T> = { isSuccessful: true; value: T; } | { isSuccessful: false; code: string; message: string; };
在 Go 中,我最初是这样开始的:
type ResponseResultError struct {
Code string `json:"code"`
Message string `json:"message"`
}
type ResponseResult[T any] struct {
Ok bool `json:"ok"`
Value T `json:"value"`
Error ResponseResultError `json:"error"`
}
但由于 Go 支持元组等特性,也许有更好的解决方案来解决这个问题?你会如何定义一个“东西”,让使用者可以创建包含以下内容的成功响应:
{ isSuccessful: true; value: T; }
或者包含以下内容的错误响应:
{ isSuccessful: false; code: string; message: string; }
?
或者我应该采用以下方式?
type InternalErrorResponseResult struct {
IsSuccessful bool `json:"isSuccessful"`
Code string `json:"code"`
Message string `json:"message"`
}
func NewInternalErrorResponseResult(code string, message string) InternalErrorResponseResult {
return InternalErrorResponseResult{
Code: code,
Message: message,
}
}
type OperationResponseResult[T any] struct {
IsSuccessful bool `json:"isSuccessful"`
Value T `json:"value"`
}
func NewOperationResponseResult[T any](isSuccessful bool, value T) OperationResponseResult[T] {
return OperationResponseResult[T]{
IsSuccessful: isSuccessful,
Value: value,
}
}
更多关于Golang中如何为API响应定义通用的成功/错误结果结构的实战教程也可以访问 https://www.itying.com/category-94-b0.html
不需要,JSON序列化本身包含大量反射操作。
更多关于Golang中如何为API响应定义通用的成功/错误结果结构的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
为你的API响应定义一个统一的结构。这个结构应该包含用于指示响应状态、消息和数据(如果适用)的字段。
在响应中包含一个状态字段,以指示API请求的结果。例如,你可以使用诸如“success”或“error”这样的值来表示整体状态。
你好 @jtuchel
你计划在哪里使用这个 Response 结构体?你需要提供更多关于此的细节,它可能是你应用程序特有的东西。
Go 语言的惯用方式是 result, err := methodName(parameters)。对于一个 HTTP 服务器,你将状态码写入头部,将结果写入响应体。因此,使用一个结构体来存储结果和任何可能的错误,并不是很常见的做法。
当然,那样做是可行的。
另一种方法是:你总是发送响应头(OK 或失败状态),对于 OK 的响应头,你将值/结果以 JSON 格式放在响应体中发送。对于非 OK 的响应头(任何类型的错误头),你在响应体中发送错误信息(或描述具体情况的特定错误信息——但不要提供内部细节,如错误堆栈跟踪或任何内部数据)。
许多公共REST API都是这样工作的。成功时仅返回状态码200和“值”作为结果。仅在出现错误时返回错误状态码和错误信息。这为API的使用者和生产者都创造了可读性强的代码。
// Go 生产者端:
result, err := myFunction(...)
if err != nil {
http.Error(w, toJSON(err), http.InternalServerError)
return
}
writeJSON(w, result)
return
// 辅助函数:
func toJSON(err error) string {
cErr := codedError{code: "UnknownError", msg: err.Message()}
errors.As(err, &cErr) // 从 codedError 获取详细信息
b, jsonErr := json.Marshal(cErr)
if jsonErr != nil {
log.Error("this should not happen...")
return err.Message()
}
return string(b)
}
// JavaScript 消费者端
const resp = await fetch('/api/...')
if (!resp.ok) {
throw new CodedError(await resp.json())
}
return resp.json()
抱歉,你说得对。我想将这个结构体用于HTTP响应体。这个结构体不会包含HTTP状态码。因此,每当我需要向API使用者返回“一个值”时,我都希望使用一个通用的响应结构。根据回答,我尝试了以下方案:
type OperationResponse[T any] struct {
IsSuccessful bool `json:"isSuccessful"`
Value T `json:"value"`
ErrorCode string `json:"errorCode,omitempty"`
Message string `json:"message,omitempty"`
}
其中 ErrorCode 表示类似 LicenseExpired 这样的含义。然后我创建了一些工具函数:
func GetInternalErrorResponse() OperationResponse[*string] {
return OperationResponse[*string]{
IsSuccessful: false,
Value: nil,
ErrorCode: "??? TODO ???",
Message: "The server encountered an unexpected condition that prevented it from fulfilling the request.",
}
}
func GetOperationFailureResponse(errorCode string, message string) OperationResponse[*string] {
return OperationResponse[*string]{
IsSuccessful: false,
Value: nil,
ErrorCode: errorCode,
Message: message,
}
}
func GetOperationSuccessResponse[T any](value T) OperationResponse[T] {
return OperationResponse[T]{
IsSuccessful: true,
Value: value,
ErrorCode: "",
Message: "",
}
}
你觉得怎么样?
你的代码在我看来没问题。问题在于你只想要一个结构体类型吗?如果是这样,你应该看看 omitEmpty:
“omitempty” 选项指定,如果字段具有空值(定义为 false、0、nil 指针、nil 接口值以及任何空数组、切片、映射或字符串),则应从编码中省略该字段。
你可以像下面这样定义你的结构体:
type Result[T any] struct {
IsSuccessful bool `json:"isSuccessful"`
Value *T `json:"value,omitempty"`
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
… 这样当它们是零值或 nil 时,就不会发送这些键。由于使用 err != nil 来判断是否出错是惯用做法,如果你愿意,也可以将构造函数代码简化成这样:
func NewResult[T any](value *T, err error) Result[T] {
if err != nil {
return Result[T]{
IsSuccessful: false,
Code: http.StatusText(http.StatusInternalServerError),
Message: err.Error(),
}
}
return Result[T]{
IsSuccessful: true,
Value: value,
}
}
因为同样地,惯用做法是 resp, err := doSomething(),在我看来,直接传递 resp, err 到这个 NewResult 函数中会很流畅。结合一些简单的测试代码:
type someItem struct {
ID int
Value string
}
func main() {
value := NewResult(&someItem{23, "Hello"}, nil)
json.NewEncoder(os.Stdout).Encode(value)
errorValue := NewResult[someItem](nil, errors.New("Problem contacting database."))
json.NewEncoder(os.Stdout).Encode(errorValue)
}
… 将产生:
{"isSuccessful":true,"value":{"ID":23,"Value":"Hello"}}
{"isSuccessful":false,"code":"Internal Server Error","message":"Problem contacting database."}
总之,只是一些想法供你参考!你也可以将其推送到一个像 SendJSON[T any](value *T, err error, w http.ResponseWriter) 这样的函数中。它可以检查 err,如果为 nil 则执行一种操作,如果不为 nil 则执行另一种操作。这是上面代码的 Playground 链接:
在Go中实现类似TypeScript的联合类型,可以使用接口和自定义类型。以下是两种常见方案:
方案1:使用接口和自定义错误类型
package main
import (
"encoding/json"
"fmt"
)
type Result[T any] interface{}
type Success[T any] struct {
IsSuccessful bool `json:"isSuccessful"`
Value T `json:"value"`
}
type Failure struct {
IsSuccessful bool `json:"isSuccessful"`
Code string `json:"code"`
Message string `json:"message"`
}
func NewSuccess[T any](value T) Success[T] {
return Success[T]{
IsSuccessful: true,
Value: value,
}
}
func NewFailure(code, message string) Failure {
return Failure{
IsSuccessful: false,
Code: code,
Message: message,
}
}
// 使用示例
func handleResult[T any](result Result[T]) {
switch r := result.(type) {
case Success[T]:
fmt.Printf("Success: %v\n", r.Value)
case Failure:
fmt.Printf("Error: %s - %s\n", r.Code, r.Message)
}
}
func main() {
// 成功响应
successResult := NewSuccess(map[string]string{"name": "John"})
successJSON, _ := json.Marshal(successResult)
fmt.Println(string(successJSON))
// 输出: {"isSuccessful":true,"value":{"name":"John"}}
// 错误响应
errorResult := NewFailure("NOT_FOUND", "Resource not found")
errorJSON, _ := json.Marshal(errorResult)
fmt.Println(string(errorJSON))
// 输出: {"isSuccessful":false,"code":"NOT_FOUND","message":"Resource not found"}
// 类型判断
handleResult(successResult)
handleResult(errorResult)
}
方案2:使用带标签的联合体(更接近你的需求)
package main
import (
"encoding/json"
"fmt"
)
type Result[T any] struct {
IsSuccessful bool `json:"isSuccessful"`
Value T `json:"value,omitempty"`
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
func NewSuccessResult[T any](value T) Result[T] {
return Result[T]{
IsSuccessful: true,
Value: value,
}
}
func NewErrorResult(code, message string) Result[any] {
return Result[any]{
IsSuccessful: false,
Code: code,
Message: message,
}
}
// 辅助函数检查结果类型
func (r Result[T]) IsSuccess() bool {
return r.IsSuccessful
}
func (r Result[T]) GetValue() (T, bool) {
return r.Value, r.IsSuccessful
}
func (r Result[T]) GetError() (string, string, bool) {
return r.Code, r.Message, !r.IsSuccessful
}
// 使用示例
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
// 成功响应
user := User{ID: 1, Name: "Alice"}
success := NewSuccessResult(user)
successJSON, _ := json.Marshal(success)
fmt.Println(string(successJSON))
// 输出: {"isSuccessful":true,"value":{"id":1,"name":"Alice"}}
// 错误响应
err := NewErrorResult("VALIDATION_ERROR", "Invalid input")
errJSON, _ := json.Marshal(err)
fmt.Println(string(errJSON))
// 输出: {"isSuccessful":false,"code":"VALIDATION_ERROR","message":"Invalid input"}
// 使用辅助函数
if success.IsSuccess() {
value, _ := success.GetValue()
fmt.Printf("User: %+v\n", value)
}
if !err.IsSuccess() {
code, msg, _ := err.GetError()
fmt.Printf("Error %s: %s\n", code, msg)
}
}
方案3:使用指针和omitempty(更简洁的JSON输出)
package main
import (
"encoding/json"
"fmt"
)
type Result[T any] struct {
IsSuccessful bool `json:"isSuccessful"`
Value *T `json:"value,omitempty"`
Error *Error `json:"error,omitempty"`
}
type Error struct {
Code string `json:"code"`
Message string `json:"message"`
}
func NewSuccess[T any](value T) Result[T] {
return Result[T]{
IsSuccessful: true,
Value: &value,
}
}
func NewError(code, message string) Result[any] {
return Result[any]{
IsSuccessful: false,
Error: &Error{
Code: code,
Message: message,
},
}
}
func main() {
// 成功响应
success := NewSuccess("operation completed")
successJSON, _ := json.Marshal(success)
fmt.Println(string(successJSON))
// 输出: {"isSuccessful":true,"value":"operation completed"}
// 错误响应
err := NewError("INTERNAL_ERROR", "Something went wrong")
errJSON, _ := json.Marshal(err)
fmt.Println(string(errJSON))
// 输出: {"isSuccessful":false,"error":{"code":"INTERNAL_ERROR","message":"Something went wrong"}}
}
第一种方案最接近TypeScript的联合类型语义,第二种方案更实用且易于序列化,第三种方案提供最干净的JSON输出。根据你的具体需求选择合适方案。


