在将JSON数组解组成指针切片时,跳过空值。

huangapple go评论79阅读模式
英文:

Skip nulls when unmarshalling JSON array into slice of pointers

问题

我有以下结构体:

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

我需要对Products及其成员以及嵌套成员(例如[]*Variant{}[]*Shipping{}等)执行多个操作。

因为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
            }
      
            // 其他操作...
        }
    }
}

有没有办法在指针切片的nil值上模拟omitempty的效果?以下是示例。

JSON输入:

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

输出,等效于:

input := Item{
    Products: []Product{ {} }, // 没有null值
}

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

英文:

I have following struct:

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

I have to perform several operations on Products and it's members and it's nested members as well which for instance are []*Variant{} or []*Shipping{} etc.

Because most of slices in Item struct are slice of pointers, my code to process this data looks like this:

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

Is there any way to mimick omitempty on nil values in slice of pointers? Example below.

JSON input:

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

output, equivalent to:

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

I tried to use omitempty on []*Property but it doesn't work. I also tried to use non-pointer values but then Go initialises every null to default struct value.

答案1

得分: 3

你可以实现一个自定义的json.Unmarshaler

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

// 如果你打算修改切片中的各个元素,请使用 []*Product。
// 如果元素是只读的,请使用 []Product。
type ProductList []*Product

// 实现json.Unmarshaler接口。
// 这将导致编码/解码器在遇到ProductList实例时调用UnmarshalJSON方法,
// 而不是执行默认的解码操作。
func (ls *ProductList) UnmarshalJSON(data []byte) error {
    // 首先,进行正常的解码
    pp := []*Product{}
    if err := json.Unmarshal(data, &pp); err != nil {
        return err
    }

    // 然后,只附加非nil值
    for _, p := range pp {
        if p != nil {
            *ls = append(*ls, p)
        }
    }

    // 完成
    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

英文:

You could implement a custom json.Unmarshaler.

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
}

Credit to @blackgreen:

With Go1.18 and up, you don't have to implement the custom unmarshaling for the other []*Variant{} and []*Shipping{} types. Instead you can use a slice type with a type parameter for the element.

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

huangapple
  • 本文由 发表于 2022年5月27日 16:56:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/72402913.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定