Go语言 将结构解封为切片[重复]的泛型类型定义

idfiyjo8  于 2023-09-28  发布在  Go
关注(0)|答案(3)|浏览(116)

这个问题已经有答案了

How to use generics in Unmarshal (go 1.18)(1个答案)
5天前关闭。
我有一个API,它经常将数组作为包含数组的对象返回。举个例子:

{
  "items": {
    "number": 3,
    "item": [
      { ... } // Not relevant
    ]
  }
}

API在几十个地方使用不同的名称执行此操作。当这种情况发生时,保证只有两个密钥:其中一个是number,另一个是数组。
这使得生成的结构非常难以使用,因为您必须不断地在不必要的字段级别之间导航。
我基本上希望我的Go界面假装它有这样的格式:

{
  "items": [
    { ... } // Not relevant
  ]
}

一种选择是为每一次出现编写一个自定义的UnmarshalJSON函数,但这似乎很麻烦,特别是考虑到这几乎出现在每一个结构中。我想到的解决方案是一个可以自己处理它的泛型类型。
我目前的尝试如下:

// NestedArray tries to pull an unnecessarily nested array upwards
type NestedArray[T any] []T

func (n *NestedArray[T]) UnmarshalJSON(bytes []byte) error {
    // First unmarshal into a map
    target := make(map[string]interface{})

    err := json.Unmarshal(bytes, &target)
    if err != nil {
        return err
    }

    // Then find the nested array (key is unknown, so go off of the type instead)
    var sliceVal interface{}
    for k, v := range target {
        if k == "number" {
            continue
        }

        rt := reflect.TypeOf(v)
        if rt.Kind() == reflect.Slice {
            sliceVal = v
            break
        }
    }

    // Missing or empty, doesn't matter - set the result to nil
    if sliceVal == nil {
        *n = nil
        return nil
    }

    // Turn back into JSON and parse into correct target
    sliceJSON, err := json.Marshal(sliceVal)
    if err != nil {
        return err
    }

    err = json.Unmarshal(sliceJSON, n)  // Error occurs here
    if err != nil {
        return err
    }

    return nil
}

使用如下:

type Item struct {
  // Not relevant
}

type Root struct {
    // Use generic type to parse a JSON object into its nested array
    Items NestedArray[Item] `json:"items,omitempty"`
}

导致以下错误:

json: cannot unmarshal array into Go struct field Root.items of type map[string]interface{}

UnmarshalJSON代码的最大部分似乎是正确的,因为我的调试器显示sliceVal是我所期望的。在解组回NestedArray[T]类型时出错。
解决这个问题的办法是什么?有比我现在做的更好的方法吗?这对我来说似乎是最干净的,但我愿意接受建议。

tcbh2hod

tcbh2hod1#

方法NestedArray[T].UnmarshalJSON递归调用自身。内部调用抛出一个错误,因为它期望bytes中的JSON对象,但它收到了一个JSON数组。通过解编为[]T而不是NestedArray[T]进行修复。
与错误无关,方法NestedArray[T].UnmarshalJSON执行了一些不必要的编码和解码。使用json.RawMessage修复。
下面是包含两个修复的代码:

func (n *NestedArray[T]) UnmarshalJSON(bytes []byte) error {
    // First unmarshal into a map
    var target map[string]json.RawMessage
    err := json.Unmarshal(bytes, &target)
    if err != nil {
        return err
    }

    // Then find the nested array (key is unknown, so go off of the type instead)
    var array json.RawMessage
    for k, v := range target {
        if k == "number" {
            continue
        }
        if len(v) > 0 && v[0] == '[' {
            array = v
            break
        }
    }

    // Missing or empty, doesn't matter - set the result to nil
    if array == nil {
        *n = nil
        return nil
    }

    // Avoid recursive call to this method by unmarshalling to a []T.
    var v []T
    err = json.Unmarshal(array, &v)
    *n = v
    return err
}

Run the code on the playground!

llew8vvj

llew8vvj2#

我让 final unmarshal使用一个中间变量,而不是方法的接收者。

// ...
    // Turn back into JSON and parse correctly
    sliceJSON, err := json.Marshal(sliceVal)
    if err != nil {
        return err
    }

    // Instead of using n here, create a new variable and use that instead
    var result []T

    err = json.Unmarshal(sliceJSON, &result)
    if err != nil {
        return err
    }

    // Then, assign that to the receiver
    *n = result
41zrol4v

41zrol4v3#

遍历JSON查找数组。解码每个数组元素并附加到接收器。

func (n *NestedArray[T]) UnmarshalJSON(data []byte) error {
    d := json.NewDecoder(bytes.NewReader(data))
    t, err := d.Token()
    if err != nil {
        return err
    }
    if t != json.Delim('{') {
        return errors.New("object expected")
    }
    for d.More() {
        // skip key
        _, err = d.Token()
        if err != nil {
            return err
        }
        // Is it an JSON array?
        t, err = d.Token()
        if t == json.Delim('[') {
            // Decode the array.
            for d.More() {
                var v T
                err := d.Decode(&v)
                if err != nil {
                    return err
                }
                *n = append(*n, v)
            }
            return nil
        }
    }
    return nil
}

https://go.dev/play/p/lLFjJpr404W
这个问题的标题是“将结构解组为切片的泛型类型别名”,但这里没有问题中的类型别名。这个问题更好的标题是“将结构解组为切片的泛型类型定义”。

相关问题