How to Marshal (using protojson package) array of proto to json in Golang?

4.7k Views Asked by At

How to achieve the following with protojson package in Golang?

Protobuf:

type ProtoResp struct {
  state         protoimpl.MessageState
  sizeCache     protoimpl.SizeCache
  unknownFields protoimpl.UnknownFields

  Xesp isActionResponse_Xesp `protobuf_oneof:"xesp"`
  TimeTakena string `protobuf:"bytes,9,opt,name=time_takena,json=timeTakena,proto3" json:"time_takena,omitempty"`
}

So, I need to marshal the array of proto into a json. For example,

var protoResps []ProtoResp

How do I marshal this protoResps?

Currently, protojson.Marshal function can marshal ProtoResp but can't marshal []ProtoResp as its slice of proto.

I also need to unmarshal this later back into []ProtoResp

Thanks in advance.

1

There are 1 best solutions below

0
On

I guess json.Marshal(protoResps) does not work for you because the document for protojson states:

This package produces a different output than the standard "encoding/json" package, which does not operate correctly on protocol buffer messages.

One workaround is still to utilize the encoding/json, but marshal/unmarshal each item with the protojson pakcage.

Option 1: Implement json.Marshaler and json.Unmarshaler

If ProtoResp is a local type, modify it to implement the Marshaler and Unmarshaler interfaces:

import (
    "google.golang.org/protobuf/encoding/protojson"
)

func (r *ProtoResp) MarshalJSON() ([]byte, error) {
    return protojson.Marshal(r)
}

func (r *ProtoResp) UnmarshalJSON(data []byte) error {
    return protojson.Unmarshal(data, r)
}

Then it's safe to use the encoding/json package to marshal/unmarshal []*ProtoResp (an instance of ProtoResp should not be copied, most of the time, you want to hold a pointer instead. I will use *ProtoResp from now on).

Option 2: Use json.RawMessage to delay the encoding/decoding

If ProtoResp is not a local type, a workaround is to use json.RawMessage to delay the encoding/decoding. This approach consumes more memory.

import (
    "encoding/json"

    "google.golang.org/protobuf/encoding/protojson"
)

func Marshal(protoResps []*ProtoResp) ([]byte, error) {
    raw := make([]json.RawMessage, len(protoResps))
    for i, p := range protoResps {
        r, err := protojson.Marshal(p)
        if err != nil {
            return nil, err
        }
        raw[i] = r
    }

    return json.Marshal(raw)
}

func Unmarshal(data []byte) ([]*ProtoResp, error) {
    var raw []json.RawMessage
    if err := json.Unmarshal(data, &raw); err != nil {
        return nil, err
    }
    protoResps := make([]*ProtoResp, len(raw))
    for i, r := range raw {
        p := &ProtoResp{}
        if err := protojson.Unmarshal(r, p); err != nil {
            return nil, err
        }
        protoResps[i] = p
    }

    return protoResps, nil
}