ChaChaPoly
ChaChaPoly

Reputation: 1851

Golang unmarshaling JSON to protobuf generated structs

I would like to reqeive a JSON response withing a client application and unmarshal this response into a struct. To ensure that the struct stays the same accross all client apps using this package, I would like to define the JSON responses as protobuf messages. I am having difficulties unmarshaling the JSON to the protobuf generated structs.

I have the following JSON data:

[
  {
    "name": "C1",
    "type": "docker"
  },
  {
    "name": "C2",
    "type": "docker"
  }
]

I have modeled my protobuf definitions like this:

syntax = "proto3";
package main;

message Container {
    string name = 1;
    string type = 2;
}

message Containers {
    repeated Container containers = 1;
}

Using this pattern with structs normaly works, but for some reason using these proto definitions causes issues. The below code demonstrates a working and a non-working example. Although one of the versions work, I am unable to use this solution, since []*Container does not satisfy the proto.Message interface.

package main

import (
    "encoding/json"
    "fmt"
    "strings"

    "github.com/gogo/protobuf/jsonpb"
)

func working(data string) ([]*Container, error) {
    var cs []*Container
    return cs, json.Unmarshal([]byte(data), &cs)
}

func notWorking(data string) (*Containers, error) {
    c := &Containers{}
    jsm := jsonpb.Unmarshaler{}
    if err := jsm.Unmarshal(strings.NewReader(data), c); err != nil {
        return nil, err
    }
    return c, nil
}

func main() {
    data := `
[
  {
    "name": "C1",
    "type": "docker"
  },
  {
    "name": "C2",
    "type": "docker"
  }
]`

    w, err := working(data)
    if err != nil {
        panic(err)
    }
    fmt.Print(w)

    nw, err := notWorking(data)
    if err != nil {
        panic(err)
    }
    fmt.Print(nw.Containers)
}

Running this gives the following output:

[name:"C1" type:"docker"  name:"C2" type:"docker" ]
panic: json: cannot unmarshal array into Go value of type map[string]json.RawMessage

goroutine 1 [running]:
main.main()
        /Users/example/go/src/github.com/example/example/main.go:46 +0x1ee

Process finished with exit code 2

Is there a way to unmarshal this JSON to Containers? Or alternatively, make []*Container to satisfy the proto.Message interface?

Upvotes: 1

Views: 8539

Answers (2)

Pallav Jha
Pallav Jha

Reputation: 3619

For the message Containers, i.e.

message Containers {
    repeated Container containers = 1;
}

The correct JSON should look like:

{
   "containers" : [
      {
        "name": "C1",
        "type": "docker"
      },
      {
        "name": "C2",
        "type": "docker"
      }
    ]
}

If you cannot change the JSON then you can utilize the func that you've created

func working(data string) ([]*Container, error) {
    var cs []*Container
    err := json.Unmarshal([]byte(data), &cs)
    // handle the error here
    return &Containers{
       containers: cs,
    }, nil
}

Upvotes: 1

sk l
sk l

Reputation: 81

You should use NewDecoder to transfer the data to jsonDecoder and then traverse the array.The code is this

 func main() {
        data := `
    [
      {
        "name": "C1",
        "type": "docker"
      },
      {
        "name": "C2",
        "type": "docker"
      }
    ]`
        jsonDecoder := json.NewDecoder(strings.NewReader(data))
        _, err := jsonDecoder.Token()
        if err != nil {
            log.Fatal(err)
        }
        var protoMessages []*pb.Container
        for jsonDecoder.More() {
            protoMessage := pb.Container{}
            err := jsonpb.UnmarshalNext(jsonDecoder, &protoMessage)
            if err != nil {
                log.Fatal(err)
            }
            protoMessages = append(protoMessages, &protoMessage)
        }
        fmt.Println("%s", protoMessages)
    }

Upvotes: 0

Related Questions