将接口{}转换为结构体的Golang方法

25

我希望改进下面代码中的getCustomerFromDTO方法,需要从interface{}创建一个struct,目前需要将该interface序列化为byte[],然后再将数组反序列化到我的struct中 - 必须有更好的方法。

我的使用情况是通过rabbitmq发送structs,并使用此通用DTO包装器发送它们,该包装器具有有关它们的附加特定于域的数据。当我从rabbit mq接收DTO时,消息的下一层解组为我的DTO,然后我需要从该DTO获取我的struct。

type Customer struct {
    Name string `json:"name"`
}

type UniversalDTO struct {
    Data interface{} `json:"data"`
    // more fields with important meta-data about the message...
}

func main() {
    // create a customer, add it to DTO object and marshal it
    customer := Customer{Name: "Ben"}
    dtoToSend := UniversalDTO{customer}
    byteData, _ := json.Marshal(dtoToSend)

    // unmarshal it (usually after receiving bytes from somewhere)
    receivedDTO := UniversalDTO{}
    json.Unmarshal(byteData, &receivedDTO)

    //Attempt to unmarshall our customer
    receivedCustomer := getCustomerFromDTO(receivedDTO.Data)
    fmt.Println(receivedCustomer)
}

func getCustomerFromDTO(data interface{}) Customer {
    customer := Customer{}
    bodyBytes, _ := json.Marshal(data)
    json.Unmarshal(bodyBytes, &customer)
    return customer
}
1个回答

32

在解组 DTO 之前,将 Data 字段设置为您期望的类型。

type Customer struct {
    Name string `json:"name"`
}

type UniversalDTO struct {
    Data interface{} `json:"data"`
    // more fields with important meta-data about the message...
}

func main() {
    // create a customer, add it to DTO object and marshal it
    customer := Customer{Name: "Ben"}
    dtoToSend := UniversalDTO{customer}
    byteData, _ := json.Marshal(dtoToSend)

    // unmarshal it (usually after receiving bytes from somewhere)
    receivedCustomer := &Customer{}
    receivedDTO := UniversalDTO{Data: receivedCustomer}
    json.Unmarshal(byteData, &receivedDTO)

    //done
    fmt.Println(receivedCustomer)
}

如果您没有在DTO解组之前初始化Data字段的能力,那么可以在解组后使用类型断言。包encoding/jsoninterface{}类型值解组为map[string]interface{},因此您的代码应该类似于:

type Customer struct {
    Name string `json:"name"`
}

type UniversalDTO struct {
    Data interface{} `json:"data"`
    // more fields with important meta-data about the message...
}

func main() {
    // create a customer, add it to DTO object and marshal it
    customer := Customer{Name: "Ben"}
    dtoToSend := UniversalDTO{customer}
    byteData, _ := json.Marshal(dtoToSend)

    // unmarshal it (usually after receiving bytes from somewhere)
    receivedDTO := UniversalDTO{}
    json.Unmarshal(byteData, &receivedDTO)

    //Attempt to unmarshall our customer
    receivedCustomer := getCustomerFromDTO(receivedDTO.Data)
    fmt.Println(receivedCustomer)
}

func getCustomerFromDTO(data interface{}) Customer {
    m := data.(map[string]interface{})
    customer := Customer{}
    if name, ok := m["name"].(string); ok {
        customer.Name = name
    }
    return customer
}

DTO的反序列化由我使用的一个包完成,对于所有接收到的消息都是相同的。在我的应用程序中,我接收到一个已经反序列化的DTO。 - Tomas
你没有修改这个包的能力吗?这是第三方的吗? - mkopriva
为了保持通用性,您可以将Data字段类型保留为interface{}。您需要做的就是将该值预初始化为您希望返回的类型。这并不会使其变得不够通用。如果您不想预初始化DTO,则可以将初始化后的值传递给包中的函数。例如mypackage.UnmarshalDTO(byteData []byte, data interface{}) ...,然后在该函数中将数据设置为数据字段,在解组完成后,您的数据变量将被正确解组。 - mkopriva
我也更喜欢预初始化值的设置方法,我想我会将其实现到我正在使用的库 - Esticade中。谢谢。 - Tomas
另一种无需知道结构体类型就能实现的方法是使用json.RawMessage而不是interface{},并在已知结构体类型时进行反序列化。 - JUSHJUSH
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接