分配空切片而不引用其类型?



我的代码调用了一个库函数,它看起来大致如下:

func Search() ([]myLibrary.SomeObject, error) {
var results []apiv17.SomeObject
// ...
if (resultsFound) {
results = append(results, someResult)
}
return results
}

…我的代码调用它,然后警察JSON。

results, err := myLibrary.Search()
bytes, err := json.Marshal(results)

现在的问题是,由于Search函数的编写方式(让我们假设我们不能改变它),如果没有结果,它将返回一个未初始化的nil片。不幸的是,没有办法将encoding/json配置为将nil片编码为[](参见正在进行讨论的提案)。

显式检查nil解决了这个问题:

results, err := myLibrary.Search()
if results == nil {
results = []apiv17.SomeObject{}
}
bytes, err := json.Marshal(results)

…但它也增加了对返回类型apiv17.SomeObject的显式依赖。这很不方便,因为该类型在库中经常更改。例如,在下一个库版本中,它可能是apiv18.SomeObject

使用上面的nil检查,每次发生这种情况时,我都必须更新我的代码。

是否有任何方法可以避免这种情况,并在不显式引用其类型的情况下为变量分配一个空的非nil切片?像这样:

results = [](type of results){}

Go 1.18

可以使用一个泛型函数捕获切片的基类型并返回长度为0的切片:

func echo[T any](v []T) []T {
return make([]T, 0)
}
func main() {
n := foo.GetFooBar()
if n == nil {
n = echo(n) // no need to refer to apiv17 here
}
bytes, _ := json.Marshal(n)
fmt.Println(string(bytes)) // prints []
}

echo中要求一个常规参数v []T的目的是允许类型推断将片[]apiv17.SomeObject与参数[]T统一起来,并将T推断为基本类型apiv17.SomeObject,这样您就可以像echo(n)一样调用它,而不需要显式的类型参数。

apiv17在编译时当然是已知的,因为它是通过myPackage传入的,所以您可以利用这一点和类型推断来避免为apiv17添加显式的import语句。

这是它在多文件游乐场上的样子:https://go.dev/play/p/4ycTkaGLFpo

类型是在bar包中声明的,但是main只导入play.ground/foo并且只使用foo.GetFooBar


Go 1.17及以下

反射。只需将echo函数从上面更改为接受interface{}参数(在Go 1.17中没有any,还记得吗?)并使用reflect.MakeSlice执行此操作:

func set(v interface{}) {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
panic("not a ptr")
}
reflect.Indirect(rv).Set(reflect.MakeSlice(rv.Type().Elem(), 0, 0))
}

然后传递一个指向切片的指针,这样你就可以通过反射来设置它的值。

func main() {
n := foo.GetFooBar()
if n == nil {
set(&n)
}
fmt.Printf("type: %T, val: %v, is nil: %tn", n, n, n == nil)
// type: []bar.FooBar, val: [], is nil: false

bytes, _ := json.Marshal(n)
fmt.Println(string(bytes)) // prints [] again
}

Go 1.17 playground: https://go.dev/play/p/4jMkr22LMF7?v=goprev

另一个答案描述了如何创建一个空片。

但是您可以更简单地解决原始问题:如果resultsnil,则不需要创建空片,无论它具有什么元素类型,JSON封送都将是[]。因此,如果resultsnil,则不需要调用json.Marshal(),只需输出"[]:

results, err := myLibrary.Search()
var bytes []byte
if results == nil {
bytes = []byte{'[', ']' } // JSON marshaling result is "[]"
} else {
bytes, err = json.Marshal(results)
// Handle error
}

最新更新