How can I initialize struct with values from array of interface in Go?

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

How can I initialize struct with values from array of interface in Go?

问题

我正在从服务器收到像[0,"on",[6,1,5,"market",45.7]]这样的消息,并将其保存到[]interface{}变量中。我想用这个数组的值初始化结构体。

我完全是Go的新手,尝试像这样做:

import "golang.org/x/net/websocket"
...
var msg []interface{}
// 服务器发送的响应:`[0,"on",[6,1,5,"market",45.7]]`
if err := websocket.Message.Receive(ws, &msg); err != nil {
    logger.Println(err)
} else {
    type Order struct {
        ID     int32
        GID    int32
        CID    int32
        Type   string
        Amount float64
    }

    // 这里的msg是[0,"on",[6,1,5,"market",45.7]]

    switch msg[1] {
    case "on":
        if rawOrder, ok := msg[2].([]interface{}); ok {
            order := Order{ID: int32(rawOrder[0]), GID: int32(rawOrder[1]), CID: int32(rawOrder[2]), Type: string(rawOrder[3]), Amount: float64(rawOrder[4])}
        }
    }
}

但是我得到了一个错误"无法将类型为'interface{}'的表达式转换为'type'的类型'int32'",下一步是为每个rawOrder[i]类型使用switch,但是太长了。
我该如何简化它?

英文:

I'm getting a message from server like [0,"on",[6,1,5,"market",45.7]] and save it to []interface{} variable. I want to initialize struct with values of this array.
I'm totally new in Go and try to do it like:

import "golang.org/x/net/websocket"
...
var msg []interface{}
// Server send response: `[0,"on",[6,1,5,"market",45.7]]`
if err := websocket.Message.Receive(ws, &msg); err != nil {
	logger.Println(err)
} else {
    type Order struct {
        ID int32,
        GID int32,
        CID int32,
        Type string,
        Amount float64
    }
    
    // here msg is [0,"on",[6,1,5,"market",45.7]]
    
    switch msg[1] {
    case "on":
        if rawOrder, ok := msg[2].([]interface{}); ok {
		    order := Order{int32(rawOrder[0]), int32(rawOrder[1]), int32(rawOrder[2]), string(rawOrder[3]), float64(rawOrder[4])}
	    }
}

But I'm getting an error "Cannot convert an expression of the type 'interface{}' to the type 'int32'" and the next step is use switch for every rawOrder[i] type, but it's toooo long.
How can I do it easilly?

答案1

得分: 0

如果你知道在websocket上使用的编解码器始终是json,你可以正式定义Order并给它一个UnmarshalJSON函数来进行解码。

import "golang.org/x/net/websocket"

type Order struct {
	ID, GID, CID int32
	Type         string
	Amount       float64
}

func (o *Order) UnmarshalJSON(data []byte) error {
	var first []json.RawMessage
	err := json.Unmarshal(data, &first)
	if err != nil {
		return fmt.Errorf("无效的订单,必须是数组:%w", err)
	}
	if len(first) != 3 {
		return fmt.Errorf("无效的订单,长度必须为3,实际为%d", len(first))
	}

	var second string
	err = json.Unmarshal(first[1], &second)
	if err != nil {
		return fmt.Errorf("无效的订单,第二个元素必须是字符串:%w", err)
	}

	switch second {
	case "on":
		var third []json.RawMessage
		err = json.Unmarshal(first[2], &third)
		if err != nil {
			return fmt.Errorf("无效的订单,第三个元素必须是数组:%w", err)
		}

		if len(third) != 5 {
			return fmt.Errorf("无效的订单,第三个元素长度必须为5,实际为%d", len(third))
		}

		for i, f := range []interface{}{&o.ID, &o.GID, &o.CID, &o.Type, &o.Amount} {
			err = json.Unmarshal(third[i], f)
			if err != nil {
				return fmt.Errorf("无效的订单,第三个元素[%d]的类型错误:%w", i, err)
			}
		}
		return nil
	}

	return fmt.Errorf("无效的订单,未知类型:%q", second)
}

...
var msg *Order
// 服务器发送的响应:`[0,"on",[6,1,5,"market",45.7]]`
if err := websocket.JSON.Receive(ws, &msg); err != nil {
    logger.Println(err)
}

// msg 现在是一个 &Order{ID:6, GID:1, CID:5, Type:"market", Amount:45.7}

UnmarshalJSON函数之所以如此庞大,是因为你的API设计不好。如果你控制服务器,应避免在同一个数组中使用混合类型,并且应避免使用数组来表示关联数据。

英文:

If you know that the codec used on the websocket will always be json, you can formally define Order and give it an UnmarshalJSON function to do the decoding for you.

import "golang.org/x/net/websocket"

type Order struct {
	ID, GID, CID int32
	Type         string
	Amount       float64
}

func (o *Order) UnmarshalJSON(data []byte) error {
	var first []json.RawMessage
	err := json.Unmarshal(data, &first)
	if err != nil {
		return fmt.Errorf("invalid order, must be array: %w", err)
	}
	if len(first) != 3 {
		return fmt.Errorf("invalid order, length must be 3, got %d", len(first))
	}

	var second string
	err = json.Unmarshal(first[1], &second)
	if err != nil {
		return fmt.Errorf("invalid order, second element must be string: %w", err)
	}

	switch second {
	case "on":
		var third []json.RawMessage
		err = json.Unmarshal(first[2], &third)
		if err != nil {
			return fmt.Errorf("invalid order, third element must be array: %w", err)
		}

		if len(third) != 5 {
			return fmt.Errorf("invalid order, element 3 length must be 5, got %d", len(third))
		}

		for i, f := range []interface{}{&o.ID, &o.GID, &o.CID, &o.Type, &o.Amount} {
			err = json.Unmarshal(third[i], f)
			if err != nil {
				return fmt.Errorf("invalid order, wrong type for element 3[%d]: %w", i, err)
			}
		}
		return nil
	}

	return fmt.Errorf("invalid order, unknown type %q", second)
}

...
var msg *Order
// Server send response: `[0,"on",[6,1,5,"market",45.7]]`
if err := websocket.JSON.Receive(ws, &msg); err != nil {
    logger.Println(err)
}

// msg is now an &Order{ID:6, GID:1, CID:5, Type:"market", Amount:45.7}

}

The reason the UnmarshalJSON function is huge is because your API is bad. If you control the Server; then you should avoid using mixed types in the same array, and you should avoid using arrays for relational data.

huangapple
  • 本文由 发表于 2021年6月18日 14:36:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/68030440.html
匿名

发表评论

匿名网友

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

确定