将 JSON 数组解组到指针片中时跳过 null 值

Skip nulls when unmarshalling JSON array into slice of pointers

提问人:mikolaj semeniuk 提问时间:5/27/2022 最后编辑:blackgreenmikolaj semeniuk 更新时间:5/27/2022 访问量:979

问:

我有以下结构:

type Item struct {
    Id       string     `json:"id"`
    Name     string     `json:"name"`
    Products []*Product `json:"products"`
}

func (i *Item) Transform(input []byte) error {
    return json.Unmarshal(input, i)
}

我必须对它的成员和它的嵌套成员执行多项操作,例如 are 或 etc。Products[]*Variant{}[]*Shipping{}

由于 struct 中的大多数切片都是指针切片,因此我用于处理此数据的代码如下所示:Item

for _, product := range i.Products {
    if product == nil {
        continue
    }
    
    for _, variant := range product.Variants {
        if variant == nil {
            continue
        }
        
        for _, shipping := range shippings {
            if shipping == nil {
                continue
            }
      
            // and so on...
        }
    }
}

有没有办法模仿指针切片中的值?下面的例子。omitemptynil

JSON输入:

{
    "products": [
        null,
        {},
        null
    ]
}

输出,相当于:

input := Item{
    Products: []Product{ {} }, // without nulls
}

我试图使用它,但它不起作用。我还尝试使用非指针值,但随后 Go 将每个 null 初始化为默认结构值。omitempty[]*Property

json go null slice 解组

评论

2赞 mkopriva 5/27/2022
仅供参考,仅适用于编码(即封送处理)。它对解码(即解组)没有影响。omitempty
1赞 mkopriva 5/27/2022
mikolaj 请参阅更新的答案,以获取有关如何避免必须为怀疑包含空值的每个切片类型实现自定义解组的建议(由 Blackgreen 提出)。

答:

3赞 mkopriva 5/27/2022 #1

您可以实现自定义 json。解组员

type Item struct {
    Id       string      `json:"id"`
    Name     string      `json:"name"`
    Products ProductList `json:"products"`
}

// Use []*Product if you intend to modify
// the individual elements in the slice.
// Use []Product if the elements are read-only.
type ProductList []*Product

// Implememt the json.Unmarshaler interface.
// This will cause the encoding/json decoder to
// invoke the UnmarshalJSON method, instead of
// performing the default decoding, whenever it
// encounters a ProductList instance.
func (ls *ProductList) UnmarshalJSON(data []byte) error {
    // first, do a normal unmarshal
    pp := []*Product{}
    if err := json.Unmarshal(data, &pp); err != nil {
        return err
    }

    // next, append only the non-nil values
    for _, p := range pp {
        if p != nil {
            *ls = append(*ls, p)
        }
    }

    // done
    return nil
}

归功于@blackgreen

使用 Go1.18 及更高版本,您不必为其他类型实现自定义解组。相反,您可以将切片类型与元素的类型参数一起使用。[]*Variant{}[]*Shipping{}

type SkipNullList[T any] []*T

func (ls *SkipNullList[T]) UnmarshalJSON(data []byte) error {
    pp := []*T{}
    if err := json.Unmarshal(data, &pp); err != nil {
        return err
    }
    for _, p := range pp {
        if p != nil {
            *ls = append(*ls, p)
        }
    }
    return nil
}

type Item struct {
    Id       string                `json:"id"`
    Name     string                `json:"name"`
    Products SkipNullList[Product] `json:"products"`
}

type Product struct {
    // ...
    Variants SkipNullList[Variant] `json:"variants"`
}

type Variant struct {
    // ...
    Shippings SkipNullList[Shipping] `json:"shippings"`
}

https://go.dev/play/p/az_9Mb_RBKX

评论

2赞 blackgreen 5/27/2022
@readers:在 SO 和其他地方,您可能会找到反对将泛型与 JSON 解组一起使用的建议,因为类型参数必须在编译时已知。但是,在这种情况下,不变量成立,因为泛型切片在用作结构字段时应始终使用具体类型实例化
0赞 Andy 5/29/2023
知道如何为 YAML 执行此操作吗?
0赞 mkopriva 5/29/2023
@Andy这几乎是一回事,但不是实现 json。取消编组,您将实现 yaml。解组员。
0赞 mkopriva 5/29/2023
@Andy go.dev/play/p/6OplJxmNH18