'Issue with Unmarshalling GRPC Response To Generic Type
I am trying to use generics to convert a JSON object into a GRPC response that has an enum, i.e.:
type GRPCResponse {
str string
enu EnumType
}
type EnumType int32
const (
Type1 EnumType = 0
Type2 EnumType = 1
)
The function for unmarshalling looks like this:
func assertHTTPResponseOK[T any](t *testing.T, endpoint string) T {
body, err := GetResponse(endpoint)
var v T
err := json.Unmarshal(body, &v)
require.Nil(t, err)
return v
}
And the code calling it looks like this:
assertHTTPResponseOK[*GRPCResponse](t, "some-endpoint")
The JSON object in question looks like this:
{"str":"hello", "enu": "Type2"}
and I am getting an error along the lines of:
json: cannot unmarshal string into Go struct field GRPCResponse.enu of type EnumType
From similar questions, I have seen that the usual advice is to use jsonpb.Unmarshal
or protojson.Unmarshal
instead of the typical json.Unmarshal
.
In changing the Unmarshal function, I also had to change T to be protoreflect.ProtoMessage
. However, this prevents me from passing a pointer to v
to Unmarshal
, because it is a pointer to the interface, not the interface. Of course, I also cannot pass in a nil pointer (not take the address of v).
So my questions are:
- Is there a way to have the pointer of this generic object satisfy the interface
protoreflect.ProtoMessage
? - Is there a better Unmarshalling function that would better fit my problem?
Solution 1:[1]
I ended up passing in the object I am unmarshalling into.
obj := new(GRPCResponse)
assertHTTPResponseOK[*GRPCResponse](t, ctx, "some-endpoint", obj)
func assertHTTPResponseOK[T protoreflect.ProtoMessage](t *testing.T, ctx context.Context, endpoint string, object T) {
body, err := GetResponse(endpoint)
require.Nil(t, err)
err = protojson.Unmarshal(body, object)
require.Nil(t, err)
}
Solution 2:[2]
Here's a generics-friendly proto unmarshaller that avoids passing the second type, at the cost of a reflective invoke to peek the type inside the pointer and call it's constructor.
var msg T // Constrained to proto.Message
// Peek the type inside T (as T= *SomeProtoMsgType)
msgType := reflect.TypeOf(msg).Elem()
// Make a new one, and throw it back into T
msg = reflect.New(msgType).Interface().(T)
errUnmarshal := proto.Unmarshal(body, msg)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Alaska |
Solution 2 | Steve Gray |