11

How can I convert grpc/protobuf3 message to JSON where the enum is represented as string?

For example, the protobuf message:

enum Level {
    WARNING = 0;
    FATAL = 1;
    SEVERE = 2;
    ...
}

message Http {
    string message = 1;
    Level level = 2;
}

Is converted by:

j, _ := json.MarshalIndent(protoMessage, "", "\t")

To:

{
    "message": "Hello world!",
    "level": 2,
}

I wish to get:

{
    "message": "Hello world!",
    "level": "SEVERE",
}

Thanks

michaelbn
  • 7,393
  • 3
  • 33
  • 46

6 Answers6

10

I found out that I should use the protobuf/jsonpb package and not the standard json package.

so:

j, _ := json.MarshalIndent(protoMessage, "", "\t")

Should be:

m := jsonpb.Marshaler{}
result, _ := m.MarshalToString(protoMessage)

Update

As noted bellow, jsonpb is depricated and the new solution is to use protojson

michaelbn
  • 7,393
  • 3
  • 33
  • 46
  • 3
    For anyone reading this in the future, be aware that `jsonpb` has been deprecated in the meantime. As recommend by others, `protojson` is the new way to go. – matm Apr 28 '22 at 08:43
8

I found some of these modules (jsonpb) to be deprecated. What worked for me was the google encoding version:

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

jsonString := protojson.Format(protoMessage)
Evan Moran
  • 3,825
  • 34
  • 20
3

Level is not a string though, it is an emum. There are really only two choices I see.

  1. Write a custom marshaller that does this for you
  2. Generate code that does this for you.

For #2, gogoprotobuf has an extension (still marked as experimental) that let's you do exactly this:

https://godoc.org/github.com/gogo/protobuf/plugin/enumstringer and https://github.com/gogo/protobuf/blob/master/extensions.md

sberry
  • 128,281
  • 18
  • 138
  • 165
  • `gogo` was not required for my specific problem. Thanks for the refer, it might help with other issues – michaelbn Apr 23 '18 at 09:09
  • 1
    The current golang protobuf lib (v1.3) is already replacing the enum values by string. However, to make the default value visible, you must set the Marshaler 'EmitDefaults' option to true: jsonpb.Marshaler{ EmitDefaults: true, } – rubens21 Nov 10 '19 at 16:28
3

For my use case, I wanted to write it to a file. Using the most recent packages as of this date, this was as close to a regular encoding/json marshal as I could get.

I used the google.golang.org/protobuf/encoding/protojson package and the .ProtoReflect().Interface() methods of my protocol buffer data structure.

package main

import (
    "io/ioutil"
    "log"

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

    "myproject/proto"
)

func main() {
    myProtoStruct := proto.MyType{}

    data, err := protojson.Marshal(myProtoStruct.ProtoReflect().Interface())
    if err != nil {
        log.Fatalf("Failed to JSON marhsal protobuf.\nError: %s", err.Error())
    }

    err = ioutil.WriteFile("my.proto.dat", data, 0600)
    if err != nil {
        log.Fatalf("Failed to write protobuf data to file.\nError: %s", err.Error())
    }

    log.Println("Written to file.")
}
Micah Parks
  • 1,504
  • 1
  • 10
  • 22
1

When it comes to serialize the json object, this would be helpful.

var msg bytes.Buffer
m := jsonpb.Marshaler{}
err := m.Marshal(&msg, event)

msg.Bytes() converts msg to byte stream.

wthrain
  • 166
  • 1
  • 7
1

this is a very old question, but I will post my solution, maybe someone on the future will also face the same issue.

firstly, jsonpb is deprecated. secondly, when you use protojson, you can set your options, which will affect the outcome JSON. In your case, the option UseEnumNumbers says how you want to marshall enums, strings or ints. Like so:

arr, err := protojson.MarshalOptions{
        UseEnumNumbers: true, // uses enums as int, not as strings
    }.Marshal(a)