如何制作界面切片和附加值

How to make interface slice and append value

我想做一个函数,从一个字符串数组中解析出一个slice(一种抽象的解析方式,可以是JSON、msgpack或XML)。这个slice的类型是不确定的,可以是[]A或者[]*A。但是现在我遇到了一个问题。你能帮帮我吗

func unmarshalSlice(strings []string, to interface{}) (err error) {
    elementType := reflect.TypeOf(to).Elem()
    to = reflect.MakeSlice(reflect.SliceOf(elementType), 0, len(strings))
    for _, str := range strings {
        value := reflect.New(elementType).Interface()
        if err = json.Unmarshal([]byte(str), value); nil != err {
            return
        }
        to = reflect.Append(to.(reflect.Value), reflect.ValueOf(value).Elem())
    }
    

    return
}

测试代码为

func TestUnmarshalSlice(t *testing.T) {
    var members []*Member
    _ = unmarshalSlice([]string{
        `{
"Value": "store"
}`, `{
"Value": "zhang"
}`,
    }, members)
    fmt.Println(members)
}

我已经更正了您的 unmarshalSlice 函数中的一些行。

首先,您必须创建发送类型的切片,然后将这些类型值附加到该切片。最后将该切片分配为已发送可分配接口的值。

并调用具有可分配类型的 unmarshalSlice 函数。

type Member struct {
    Value string
}
func TestUnmarshalSlice(t *testing.T) {
    var members []Member
    _ = unmarshalSlice([]string{
        `{
"Value": "store"
}`, `{
"Value": "zhang"
}`,
    }, &members)
    fmt.Println(members[0], members[1]) //Output: {store} {zhang}
}

func TestUnmarshalPointerSlice(t *testing.T) {
    var members []*Member
    _ = unmarshalSlice([]string{
        `{
"Value": "store"
}`, `{
"Value": "zhang"
}`,
    }, &members)
    fmt.Println(members[0], members[1]) //Output: &{store} &{zhang}
}

func unmarshalSlice(strings []string, to interface{}) (err error) {
    elementType := reflect.TypeOf(to).Elem().Elem()
    // create slice of *Member{}
    o := reflect.MakeSlice(reflect.TypeOf(to).Elem(), 0, len(strings))
    for _, str := range strings {
        // create empty assignable Member{}
        value := reflect.New(elementType).Interface()
        if err = json.Unmarshal([]byte(str), value); nil != err {
            return
        }
        // append *Member{} to slice
        o = reflect.Append(o, reflect.ValueOf(value).Elem())
    }

    // set created slice to the value of "to"
    reflect.ValueOf(to).Elem().Set(o)

    return
}

你可以运行测试here

我不相信您期望从该功能获得的灵活性是一件好事...

无论如何,很快(当泛型被添加到 Go 1.18 中的语言中时),您将能够编写一个签名略有不同的泛型函数来执行您想要的操作 ,而无需求助于反射:

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type Member struct {
    Value string
}

func unmarshalSlice[T any](ss []string) ([]T, error) {
    ts := make([]T, 0, len(ss))
    for _, str := range ss {
        var t T
        if err := json.Unmarshal([]byte(str), &t); err != nil {
            return nil, err
        }
        ts = append(ts, t)
    }
    return ts, nil
}

func main() {
    input := []string{
        `{"Value": "store"}`,
        `{"Value": "zhang"}`,
    }

    members, err := unmarshalSlice[Member](input)
    if err != nil {
        fmt.Fprint(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Println(members)

    pmembers, err := unmarshalSlice[*Member](input)
    if err != nil {
        fmt.Fprint(os.Stderr, err)
        os.Exit(1)
    }
    for _, m := range pmembers {
        fmt.Println(*m)
    }
}

输出:

[{store} {zhang}]
{store}
{zhang}

(Playground))

您不需要那么复杂的代码。看看这个非常简单的解决方案

package main

import (
    "encoding/json"
    "fmt"
    "strings"
)

type Member struct {
    Value string
}

func main() {
    inputs := []string{
        `{
"Value": "store"
}`, `{
"Value": "zhang"
}`,
    }
    var members []Member
    s := strings.Join(inputs, ",")
    s = strings.TrimSuffix(s, ",")
    s = fmt.Sprintf("[%v]", s)
    json.Unmarshal([]byte(s), &members)
    fmt.Println(members[0], members[1])

    var out []*Member
    json.Unmarshal([]byte(s), &out)
    fmt.Println(out[0], out[1])
}