Skip to content

Commit

Permalink
Switch jsonpb.Marshaler to use camelCase by default.
Browse files Browse the repository at this point in the history
This now matches the official proto3 JSON spec at
https://developers.google.com/protocol-buffers/docs/proto3#json.
Note that jsonpb.Unmarshaler accepts both field name forms.

There is a new Marshaler.OrigName field that can be used to preserve
the old behaviour.

This may require using protoc version 3.0.0 beta2 or later.
  • Loading branch information
dsymonds committed Feb 10, 2016
1 parent d20896f commit 001690d
Show file tree
Hide file tree
Showing 9 changed files with 1,047 additions and 749 deletions.
49 changes: 40 additions & 9 deletions jsonpb/jsonpb.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ type Marshaler struct {
// value, and for newlines to be appear between fields and array
// elements.
Indent string

// Whether to use the original (.proto) name for fields.
OrigName bool
}

// Marshal marshals a protocol buffer into JSON.
Expand Down Expand Up @@ -149,7 +152,7 @@ func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent string
value = sv.Field(0)
valueField = sv.Type().Field(0)
}
prop := jsonProperties(valueField)
prop := jsonProperties(valueField, m.OrigName)
if !firstField {
m.writeSep(out)
}
Expand Down Expand Up @@ -182,7 +185,7 @@ func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent string
value := reflect.ValueOf(ext)
var prop proto.Properties
prop.Parse(desc.Tag)
prop.OrigName = fmt.Sprintf("[%s]", desc.Name)
prop.JSONName = fmt.Sprintf("[%s]", desc.Name)
if !firstField {
m.writeSep(out)
}
Expand Down Expand Up @@ -217,7 +220,7 @@ func (m *Marshaler) marshalField(out *errWriter, prop *proto.Properties, v refle
out.write(m.Indent)
}
out.write(`"`)
out.write(prop.OrigName)
out.write(prop.JSONName)
out.write(`":`)
if m.Indent != "" {
out.write(" ")
Expand Down Expand Up @@ -395,13 +398,24 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
if strings.HasPrefix(ft.Name, "XXX_") {
continue
}
fieldName := jsonProperties(ft).OrigName
// Be liberal in what names we accept; both orig_name and camelName are okay.
fieldNames := acceptedJSONFieldNames(ft)

valueForField, ok := jsonFields[fieldName]
if !ok {
vOrig, okOrig := jsonFields[fieldNames.orig]
vCamel, okCamel := jsonFields[fieldNames.camel]
if !okOrig && !okCamel {
continue
}
delete(jsonFields, fieldName)
// If, for some reason, both are present in the data, favour the camelName.
var valueForField json.RawMessage
if okOrig {
valueForField = vOrig
delete(jsonFields, fieldNames.orig)
}
if okCamel {
valueForField = vCamel
delete(jsonFields, fieldNames.camel)
}

// Handle enums, which have an underlying type of int32,
// and may appear as strings. We do this while handling
Expand Down Expand Up @@ -510,13 +524,30 @@ func unmarshalValue(target reflect.Value, inputValue json.RawMessage) error {
return json.Unmarshal(inputValue, target.Addr().Interface())
}

// jsonProperties returns parsed proto.Properties for the field.
func jsonProperties(f reflect.StructField) *proto.Properties {
// jsonProperties returns parsed proto.Properties for the field and corrects JSONName attribute.
func jsonProperties(f reflect.StructField, origName bool) *proto.Properties {
var prop proto.Properties
prop.Init(f.Type, f.Name, f.Tag.Get("protobuf"), &f)
if origName || prop.JSONName == "" {
prop.JSONName = prop.OrigName
}
return &prop
}

type fieldNames struct {
orig, camel string
}

func acceptedJSONFieldNames(f reflect.StructField) fieldNames {
var prop proto.Properties
prop.Init(f.Type, f.Name, f.Tag.Get("protobuf"), &f)
opts := fieldNames{orig: prop.OrigName, camel: prop.OrigName}
if prop.JSONName != "" {
opts.camel = prop.JSONName
}
return opts
}

// extendableProto is an interface implemented by any protocol buffer that may be extended.
type extendableProto interface {
proto.Message
Expand Down
130 changes: 67 additions & 63 deletions jsonpb/jsonpb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,31 +62,31 @@ var (
}

simpleObjectJSON = `{` +
`"o_bool":true,` +
`"o_int32":-32,` +
`"o_int64":"-6400000000",` +
`"o_uint32":32,` +
`"o_uint64":"6400000000",` +
`"o_sint32":-13,` +
`"o_sint64":"-2600000000",` +
`"o_float":3.14,` +
`"o_double":6.02214179e+23,` +
`"o_string":"hello \"there\"",` +
`"o_bytes":"YmVlcCBib29w"` +
`"oBool":true,` +
`"oInt32":-32,` +
`"oInt64":"-6400000000",` +
`"oUint32":32,` +
`"oUint64":"6400000000",` +
`"oSint32":-13,` +
`"oSint64":"-2600000000",` +
`"oFloat":3.14,` +
`"oDouble":6.02214179e+23,` +
`"oString":"hello \"there\"",` +
`"oBytes":"YmVlcCBib29w"` +
`}`

simpleObjectPrettyJSON = `{
"o_bool": true,
"o_int32": -32,
"o_int64": "-6400000000",
"o_uint32": 32,
"o_uint64": "6400000000",
"o_sint32": -13,
"o_sint64": "-2600000000",
"o_float": 3.14,
"o_double": 6.02214179e+23,
"o_string": "hello \"there\"",
"o_bytes": "YmVlcCBib29w"
"oBool": true,
"oInt32": -32,
"oInt64": "-6400000000",
"oUint32": 32,
"oUint64": "6400000000",
"oSint32": -13,
"oSint64": "-2600000000",
"oFloat": 3.14,
"oDouble": 6.02214179e+23,
"oString": "hello \"there\"",
"oBytes": "YmVlcCBib29w"
}`

repeatsObject = &pb.Repeats{
Expand All @@ -104,65 +104,65 @@ var (
}

repeatsObjectJSON = `{` +
`"r_bool":[true,false,true],` +
`"r_int32":[-3,-4,-5],` +
`"r_int64":["-123456789","-987654321"],` +
`"r_uint32":[1,2,3],` +
`"r_uint64":["6789012345","3456789012"],` +
`"r_sint32":[-1,-2,-3],` +
`"r_sint64":["-6789012345","-3456789012"],` +
`"r_float":[3.14,6.28],` +
`"r_double":[2.99792458e+08,6.62606957e-34],` +
`"r_string":["happy","days"],` +
`"r_bytes":["c2tpdHRsZXM=","bSZtJ3M="]` +
`"rBool":[true,false,true],` +
`"rInt32":[-3,-4,-5],` +
`"rInt64":["-123456789","-987654321"],` +
`"rUint32":[1,2,3],` +
`"rUint64":["6789012345","3456789012"],` +
`"rSint32":[-1,-2,-3],` +
`"rSint64":["-6789012345","-3456789012"],` +
`"rFloat":[3.14,6.28],` +
`"rDouble":[2.99792458e+08,6.62606957e-34],` +
`"rString":["happy","days"],` +
`"rBytes":["c2tpdHRsZXM=","bSZtJ3M="]` +
`}`

repeatsObjectPrettyJSON = `{
"r_bool": [
"rBool": [
true,
false,
true
],
"r_int32": [
"rInt32": [
-3,
-4,
-5
],
"r_int64": [
"rInt64": [
"-123456789",
"-987654321"
],
"r_uint32": [
"rUint32": [
1,
2,
3
],
"r_uint64": [
"rUint64": [
"6789012345",
"3456789012"
],
"r_sint32": [
"rSint32": [
-1,
-2,
-3
],
"r_sint64": [
"rSint64": [
"-6789012345",
"-3456789012"
],
"r_float": [
"rFloat": [
3.14,
6.28
],
"r_double": [
"rDouble": [
2.99792458e+08,
6.62606957e-34
],
"r_string": [
"rString": [
"happy",
"days"
],
"r_bytes": [
"rBytes": [
"c2tpdHRsZXM=",
"bSZtJ3M="
]
Expand All @@ -182,46 +182,46 @@ var (
}

complexObjectJSON = `{"color":"GREEN",` +
`"r_color":["RED","GREEN","BLUE"],` +
`"simple":{"o_int32":-32},` +
`"r_simple":[{"o_int32":-32},{"o_int64":"25"}],` +
`"repeats":{"r_string":["roses","red"]},` +
`"r_repeats":[{"r_string":["roses","red"]},{"r_string":["violets","blue"]}]` +
`"rColor":["RED","GREEN","BLUE"],` +
`"simple":{"oInt32":-32},` +
`"rSimple":[{"oInt32":-32},{"oInt64":"25"}],` +
`"repeats":{"rString":["roses","red"]},` +
`"rRepeats":[{"rString":["roses","red"]},{"rString":["violets","blue"]}]` +
`}`

complexObjectPrettyJSON = `{
"color": "GREEN",
"r_color": [
"rColor": [
"RED",
"GREEN",
"BLUE"
],
"simple": {
"o_int32": -32
"oInt32": -32
},
"r_simple": [
"rSimple": [
{
"o_int32": -32
"oInt32": -32
},
{
"o_int64": "25"
"oInt64": "25"
}
],
"repeats": {
"r_string": [
"rString": [
"roses",
"red"
]
},
"r_repeats": [
"rRepeats": [
{
"r_string": [
"rString": [
"roses",
"red"
]
},
{
"r_string": [
"rString": [
"violets",
"blue"
]
Expand All @@ -235,7 +235,7 @@ var (

colorListPrettyJSON = `{
"color": 1000,
"r_color": [
"rColor": [
"RED"
]
}`
Expand Down Expand Up @@ -306,12 +306,14 @@ var marshalingTests = []struct {
`{"buggy":{"1234":"yup"}}`},
{"map<bool, bool>", marshaler, &pb.Mappy{Booly: map[bool]bool{false: true}}, `{"booly":{"false":true}}`},
{"proto2 map<int64, string>", marshaler, &pb.Maps{MInt64Str: map[int64]string{213: "cat"}},
`{"m_int64_str":{"213":"cat"}}`},
`{"mInt64Str":{"213":"cat"}}`},
{"proto2 map<bool, Object>", marshaler,
&pb.Maps{MBoolSimple: map[bool]*pb.Simple{true: &pb.Simple{OInt32: proto.Int32(1)}}},
`{"m_bool_simple":{"true":{"o_int32":1}}}`},
`{"mBoolSimple":{"true":{"oInt32":1}}}`},
{"oneof, not set", marshaler, &pb.MsgWithOneof{}, `{}`},
{"oneof, set", marshaler, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Title{"Grand Poobah"}}, `{"title":"Grand Poobah"}`},
{"force orig_name", Marshaler{OrigName: true}, &pb.Simple{OInt32: proto.Int32(4)},
`{"o_int32":4}`},
{"proto2 extension", marshaler, realNumber, realNumberJSON},
}

Expand Down Expand Up @@ -344,12 +346,14 @@ var unmarshalingTests = []struct {
{"unknown enum value object",
"{\n \"color\": 1000,\n \"r_color\": [\n \"RED\"\n ]\n}",
&pb.Widget{Color: pb.Widget_Color(1000).Enum(), RColor: []pb.Widget_Color{pb.Widget_RED}}},
{"unquoted int64 object", `{"o_int64":-314}`, &pb.Simple{OInt64: proto.Int64(-314)}},
{"unquoted uint64 object", `{"o_uint64":123}`, &pb.Simple{OUint64: proto.Uint64(123)}},
{"unquoted int64 object", `{"oInt64":-314}`, &pb.Simple{OInt64: proto.Int64(-314)}},
{"unquoted uint64 object", `{"oUint64":123}`, &pb.Simple{OUint64: proto.Uint64(123)}},
{"map<int64, int32>", `{"nummy":{"1":2,"3":4}}`, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}},
{"map<string, string>", `{"strry":{"\"one\"":"two","three":"four"}}`, &pb.Mappy{Strry: map[string]string{`"one"`: "two", "three": "four"}}},
{"map<int32, Object>", `{"objjy":{"1":{"dub":1}}}`, &pb.Mappy{Objjy: map[int32]*pb.Simple3{1: &pb.Simple3{Dub: 1}}}},
{"oneof", `{"salary":31000}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Salary{31000}}},
{"orig_name input", `{"o_bool":true}`, &pb.Simple{OBool: proto.Bool(true)}},
{"camelName input", `{"oBool":true}`, &pb.Simple{OBool: proto.Bool(true)}},
}

func TestUnmarshaling(t *testing.T) {
Expand Down
37 changes: 19 additions & 18 deletions jsonpb/jsonpb_test_proto/more_test_objects.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 001690d

Please sign in to comment.