25

I want to improve the getCustomerFromDTO method in the code below, I need to create a struct from an interface{} and currently i need to marshall that interface to byte[] and then unmarshal the array to my struct - there must be a better way.

My use case is that I send structs via rabbitmq and to send them I use this general DTO wrapper that has additional domain specific data about them. When I receive the DTO from rabbit mq one layer below the message is unmarshaled to my DTO and then i need to get my struct from that DTO.

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 Answer 1

32

Before unmarshaling the DTO, set the Data field to the type you expect.

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

If you don't have the ability to initialize the Data field on the DTO before it's unmarshaled, you can use type assertion after the unmarshaling. Package encoding/json unamrshals interface{} type values into a map[string]interface{}, so your code would look something like this:

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
}
Sign up to request clarification or add additional context in comments.

7 Comments

The DTO unmarshaling is done by a package I use and it's the same for all received messages. In my application I receive an already unmarshalled DTO
And you don't have the ability to modify this package? is it 3rd party?
I can modify it, but since it's a general purpose package at the time i unmarshal the DTO i don't know the type of struct it carries in it's data field
See updated answer. Btw to keep it general purpose you can keep the Data field type as interface{} and all you need to do is to pre-initialize the value to a type that you expect back. This does not make it less general. You can pass the initialized value to a func in your package if you don't want to pre-initialize the DTO as well.. For example mypackage.UnmarshalDTO(byteData []byte, data interface{}) ... then in that func set data to the Data field and after unmarshal is done your data var will unmarshaled correctly.
@Tomas here's an example where the mypackageGetDTOFromSomewhere func does not need to know the type of the Data field, only the caller of that func needs to know the type.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.