gpt4 book ai didi

Generic type definition to unmarshal struct as slice [duplicate](将结构解组为Slice[Duplate]的泛型类型定义)

转载 作者:bug小助手 更新时间:2023-10-25 13:04:41 30 4
gpt4 key购买 nike




I have an API that often returns arrays as an object that contains an array. Take the example below:

我有一个API,它经常将数组作为包含数组的对象返回。下面是一个例子:


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

The API does this in dozens of places with a different name each time. It is guaranteed that when this occurs there are only two keys: one of them being number and the other being the array.

API在数十个地方执行此操作,每次都使用不同的名称。可以保证,当发生这种情况时,只有两个键:一个是数字,另一个是数组。


This makes the resulting structs rather unpleasant to work with, as you constantly have to navigate through levels of unnecessary fields.

这使得生成的结构非常难于使用,因为您必须不断地在不必要的字段级别中导航。


I essentially want my Go interface to pretend it had this format instead:

我实际上想让我的Go界面假装它的格式是这样的:


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

One option is to write a custom UnmarshalJSON function for every single occurrence, but this seems cumbersome, especially considering this appears in nearly every struct. The solution I had in mind is a generic type that can handle it on its own.

一种选择是为每个事件编写一个定制的UnmarshalJSON函数,但这似乎很麻烦,特别是考虑到这几乎出现在每个结构中。我心目中的解决方案是一种可以自己处理的泛型类型。


My current attempt is below:

我目前的尝试如下:


// 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
}

Using it as follows:

按如下方式使用它:


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"`
}

Results in the following error:

导致以下错误:


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

The biggest part of UnmarshalJSON code seems correct, as my debugger shows me that sliceVal is what I'd expect it to be. It errors when unmarshalling back into the NestedArray[T] type.

UnmarshalJSON代码的最大部分似乎是正确的,因为我的调试器向我展示了sliceVal是我所期望的。将数据解组回NestedArray[T]类型时出错。


What could be the solution to this problem? Is there a better way to go about it than what I'm currently doing? This seemed the cleanest to me but I'm open to suggestions.

这个问题的解决方案是什么?有没有比我现在所做的更好的办法呢?这对我来说似乎是最干净的,但我愿意接受建议。


更多回答
优秀答案推荐

The method NestedArray[T].UnmarshalJSON calls itself recursively. The inner call throws an error because it's expecting a JSON object in bytes, but it received a JSON array. Fix by unmarshalling to a []T instead of a NestedArray[T].

方法NestedArray[T].UnmarshalJSON递归地调用自身。内部调用抛出一个错误,因为它需要一个以字节为单位的JSON对象,但它收到了一个JSON数组。通过解组到[]T而不是嵌套数组[T]进行修复。


Unrelated to the error, the method NestedArray[T].UnmarshalJSON does some unnecessary encoding and decoding. Fix by using json.RawMessage.

与错误无关,方法NestedArray[T].UnmarshalJSON执行了一些不必要的编码和解码。使用json.RawMessage修复。


Here's the code with both fixes:

以下是包含这两个修复的代码:


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!.

在操场上运行代码!



I got it it work by making the final unmarshal use an intermediary variable as opposed to the receiver of the method.

我让最终的解组使用一个中间变量,而不是方法的接收方,从而使其正常工作。


    // ...
// 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


Loop through the JSON looking for the array. Decode each array element and append to the receiver.

循环遍历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

Https://go.dev/play/p/lLFjJpr404W


The question's title is "Generic type alias to unmarshal struct as slice", but here is not a type alias in the question. A better title for the question is "Generic type definition to unmarshal struct as slice".

问题的标题是“将结构解组为切片的泛型类型别名”,但问题中没有类型别名。这个问题的更好标题是“将结构解组为切片的泛型类型定义”。


更多回答

Interesting, I didn't know about RawMessage. Will keep it in mind for the future.

有趣的是,我不知道RawMessage。我会记住它的未来。

As mentioned in the post multiple times, the key is unknown. This pattern occurs in multiple places, each of them having a different key. I want a generic way to do it. Yours calls it item which only works for the specific case in my example, but not all the others.

正如帖子中多次提到的那样,钥匙是未知的。这种模式出现在多个地方,每个地方都有不同的键。我想要一种通用的方法来做这件事。您的应用程序只对我的示例中的特定情况有效,而不是对其他所有情况都有效。

That's an interesting approach. In which aspects is this better or worse than what I posted myself as the answer?

这是一种有趣的方法。在哪些方面,这比我自己发布的答案更好或更差?

I was unaware of the other answer at the time I updated this answer. Here's my comparison now that I've seen the other answer: The other answer unmarshals, marshals and unmarshals again. This answer unmarshals once.

当我更新这个答案时,我并不知道另一个答案。下面是我的比较,因为我已经看到了另一个答案:另一个答案是解组,再解组,再解组。这个答案只有一次解组。

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com