encoding/jsonpb: add support for unmarshaling wrapper and struct types
Also, fixed unmarshaling of map messages where non-fatal errors were not
propagated up.
Change-Id: I06415b4a4ccd12135f0fdfaa38ccda54866139e7
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/168997
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/encoding/jsonpb/decode.go b/encoding/jsonpb/decode.go
index 5ea6fb8..8436c38 100644
--- a/encoding/jsonpb/decode.go
+++ b/encoding/jsonpb/decode.go
@@ -131,13 +131,10 @@
// unmarshalMessage unmarshals a message into the given protoreflect.Message.
func (d decoder) unmarshalMessage(m pref.Message) error {
var nerr errors.NonFatal
- var reqNums set.Ints
- var seenNums set.Ints
- msgType := m.Type()
- knownFields := m.KnownFields()
- fieldDescs := msgType.Fields()
- xtTypes := knownFields.ExtensionTypes()
+ if isCustomType(m.Type().FullName()) {
+ return d.unmarshalCustomType(m)
+ }
jval, err := d.Read()
if !nerr.Merge(err) {
@@ -147,6 +144,24 @@
return unexpectedJSONError{jval}
}
+ if err := d.unmarshalFields(m); !nerr.Merge(err) {
+ return err
+ }
+
+ return nerr.E
+}
+
+// unmarshalFields unmarshals the fields into the given protoreflect.Message.
+func (d decoder) unmarshalFields(m pref.Message) error {
+ var nerr errors.NonFatal
+ var reqNums set.Ints
+ var seenNums set.Ints
+
+ msgType := m.Type()
+ knownFields := m.KnownFields()
+ fieldDescs := msgType.Fields()
+ xtTypes := knownFields.ExtensionTypes()
+
Loop:
for {
// Read field name.
@@ -205,20 +220,21 @@
}
seenNums.Set(num)
- // No need to set values for JSON null.
- if d.Peek() == json.Null {
+ // No need to set values for JSON null unless the field type is
+ // google.protobuf.Value.
+ if d.Peek() == json.Null && !isKnownValue(fd) {
d.Read()
continue
}
if cardinality := fd.Cardinality(); cardinality == pref.Repeated {
// Map or list fields have cardinality of repeated.
- if err := d.unmarshalRepeated(fd, knownFields); !nerr.Merge(err) {
+ if err := d.unmarshalRepeated(knownFields, fd); !nerr.Merge(err) {
return errors.New("%v|%q: %v", fd.FullName(), name, err)
}
} else {
// Required or optional fields.
- if err := d.unmarshalSingular(fd, knownFields); !nerr.Merge(err) {
+ if err := d.unmarshalSingular(knownFields, fd); !nerr.Merge(err) {
return errors.New("%v|%q: %v", fd.FullName(), name, err)
}
if cardinality == pref.Required {
@@ -257,7 +273,7 @@
// unmarshalSingular unmarshals to the non-repeated field specified by the given
// FieldDescriptor.
-func (d decoder) unmarshalSingular(fd pref.FieldDescriptor, knownFields pref.KnownFields) error {
+func (d decoder) unmarshalSingular(knownFields pref.KnownFields, fd pref.FieldDescriptor) error {
var val pref.Value
var err error
num := fd.Number()
@@ -493,16 +509,16 @@
}
// unmarshalRepeated unmarshals into a repeated field.
-func (d decoder) unmarshalRepeated(fd pref.FieldDescriptor, knownFields pref.KnownFields) error {
+func (d decoder) unmarshalRepeated(knownFields pref.KnownFields, fd pref.FieldDescriptor) error {
var nerr errors.NonFatal
num := fd.Number()
val := knownFields.Get(num)
if !fd.IsMap() {
- if err := d.unmarshalList(fd, val.List()); !nerr.Merge(err) {
+ if err := d.unmarshalList(val.List(), fd); !nerr.Merge(err) {
return err
}
} else {
- if err := d.unmarshalMap(fd, val.Map()); !nerr.Merge(err) {
+ if err := d.unmarshalMap(val.Map(), fd); !nerr.Merge(err) {
return err
}
}
@@ -510,7 +526,7 @@
}
// unmarshalList unmarshals into given protoreflect.List.
-func (d decoder) unmarshalList(fd pref.FieldDescriptor, list pref.List) error {
+func (d decoder) unmarshalList(list pref.List, fd pref.FieldDescriptor) error {
var nerr errors.NonFatal
jval, err := d.Read()
if !nerr.Merge(err) {
@@ -555,7 +571,7 @@
}
// unmarshalMap unmarshals into given protoreflect.Map.
-func (d decoder) unmarshalMap(fd pref.FieldDescriptor, mmap pref.Map) error {
+func (d decoder) unmarshalMap(mmap pref.Map, fd pref.FieldDescriptor) error {
var nerr errors.NonFatal
jval, err := d.Read()
@@ -579,11 +595,12 @@
switch valDesc.Kind() {
case pref.MessageKind, pref.GroupKind:
unmarshalMapValue = func() (pref.Value, error) {
+ var nerr errors.NonFatal
m := mmap.NewMessage()
- if err := d.unmarshalMessage(m); err != nil {
+ if err := d.unmarshalMessage(m); !nerr.Merge(err) {
return pref.Value{}, err
}
- return pref.ValueOf(m), nil
+ return pref.ValueOf(m), nerr.E
}
}
diff --git a/encoding/jsonpb/decode_test.go b/encoding/jsonpb/decode_test.go
index 2552275..725c5a8 100644
--- a/encoding/jsonpb/decode_test.go
+++ b/encoding/jsonpb/decode_test.go
@@ -16,6 +16,8 @@
"github.com/golang/protobuf/v2/proto"
preg "github.com/golang/protobuf/v2/reflect/protoregistry"
"github.com/golang/protobuf/v2/runtime/protoiface"
+
+ knownpb "github.com/golang/protobuf/v2/types/known"
)
func init() {
@@ -754,12 +756,28 @@
},
},
}, {
- desc: "repeated scalars containing invalid type",
+ desc: "repeated string contains invalid UTF8",
+ inputMessage: &pb2.Repeats{},
+ inputText: `{"rptString": ["` + "abc\xff" + `"]}`,
+ wantMessage: &pb2.Repeats{
+ RptString: []string{"abc\xff"},
+ },
+ wantErr: true,
+ }, {
+ desc: "repeated messages contain invalid UTF8",
+ inputMessage: &pb2.Nests{},
+ inputText: `{"rptNested": [{"optString": "` + "abc\xff" + `"}]}`,
+ wantMessage: &pb2.Nests{
+ RptNested: []*pb2.Nested{{OptString: scalar.String("abc\xff")}},
+ },
+ wantErr: true,
+ }, {
+ desc: "repeated scalars contain invalid type",
inputMessage: &pb2.Repeats{},
inputText: `{"rptString": ["hello", null, "world"]}`,
wantErr: true,
}, {
- desc: "repeated messages containing invalid type",
+ desc: "repeated messages contain invalid type",
inputMessage: &pb2.Nests{},
inputText: `{"rptNested": [{}, null]}`,
wantErr: true,
@@ -938,6 +956,36 @@
}`,
wantErr: true,
}, {
+ desc: "map contains contains message value with invalid UTF8",
+ inputMessage: &pb3.Maps{},
+ inputText: `{
+ "strToNested": {
+ "hello": {
+ "sString": "` + "abc\xff" + `"
+ }
+ }
+}`,
+ wantMessage: &pb3.Maps{
+ StrToNested: map[string]*pb3.Nested{
+ "hello": {SString: "abc\xff"},
+ },
+ },
+ wantErr: true,
+ }, {
+ desc: "map key contains invalid UTF8",
+ inputMessage: &pb3.Maps{},
+ inputText: `{
+ "strToNested": {
+ "` + "abc\xff" + `": {}
+ }
+}`,
+ wantMessage: &pb3.Maps{
+ StrToNested: map[string]*pb3.Nested{
+ "abc\xff": {},
+ },
+ },
+ wantErr: true,
+ }, {
desc: "extensions of non-repeated fields",
inputMessage: &pb2.Extensions{},
inputText: `{
@@ -947,8 +995,8 @@
"[pb2.opt_ext_bool]": true,
"[pb2.opt_ext_nested]": {
"optString": "nested in an extension",
- "opt_nested": {
- "opt_string": "another nested in an extension"
+ "optNested": {
+ "optString": "another nested in an extension"
}
},
"[pb2.opt_ext_string]": "extension field",
@@ -1146,6 +1194,374 @@
})
return m
}(),
+ }, {
+ desc: "Empty",
+ inputMessage: &knownpb.Empty{},
+ inputText: `{}`,
+ wantMessage: &knownpb.Empty{},
+ }, {
+ desc: "Empty contains unknown",
+ inputMessage: &knownpb.Empty{},
+ inputText: `{"unknown": null}`,
+ wantErr: true,
+ }, {
+ desc: "BoolValue false",
+ inputMessage: &knownpb.BoolValue{},
+ inputText: `false`,
+ wantMessage: &knownpb.BoolValue{},
+ }, {
+ desc: "BoolValue true",
+ inputMessage: &knownpb.BoolValue{},
+ inputText: `true`,
+ wantMessage: &knownpb.BoolValue{Value: true},
+ }, {
+ desc: "BoolValue invalid value",
+ inputMessage: &knownpb.BoolValue{},
+ inputText: `{}`,
+ wantErr: true,
+ }, {
+ desc: "Int32Value",
+ inputMessage: &knownpb.Int32Value{},
+ inputText: `42`,
+ wantMessage: &knownpb.Int32Value{Value: 42},
+ }, {
+ desc: "Int32Value in JSON string",
+ inputMessage: &knownpb.Int32Value{},
+ inputText: `"1.23e3"`,
+ wantMessage: &knownpb.Int32Value{Value: 1230},
+ }, {
+ desc: "Int64Value",
+ inputMessage: &knownpb.Int64Value{},
+ inputText: `"42"`,
+ wantMessage: &knownpb.Int64Value{Value: 42},
+ }, {
+ desc: "UInt32Value",
+ inputMessage: &knownpb.UInt32Value{},
+ inputText: `42`,
+ wantMessage: &knownpb.UInt32Value{Value: 42},
+ }, {
+ desc: "UInt64Value",
+ inputMessage: &knownpb.UInt64Value{},
+ inputText: `"42"`,
+ wantMessage: &knownpb.UInt64Value{Value: 42},
+ }, {
+ desc: "FloatValue",
+ inputMessage: &knownpb.FloatValue{},
+ inputText: `1.02`,
+ wantMessage: &knownpb.FloatValue{Value: 1.02},
+ }, {
+ desc: "FloatValue exceeds max limit",
+ inputMessage: &knownpb.FloatValue{},
+ inputText: `1.23+40`,
+ wantErr: true,
+ }, {
+ desc: "FloatValue Infinity",
+ inputMessage: &knownpb.FloatValue{},
+ inputText: `"-Infinity"`,
+ wantMessage: &knownpb.FloatValue{Value: float32(math.Inf(-1))},
+ }, {
+ desc: "DoubleValue",
+ inputMessage: &knownpb.DoubleValue{},
+ inputText: `1.02`,
+ wantMessage: &knownpb.DoubleValue{Value: 1.02},
+ }, {
+ desc: "DoubleValue Infinity",
+ inputMessage: &knownpb.DoubleValue{},
+ inputText: `"Infinity"`,
+ wantMessage: &knownpb.DoubleValue{Value: math.Inf(+1)},
+ }, {
+ desc: "StringValue empty",
+ inputMessage: &knownpb.StringValue{},
+ inputText: `""`,
+ wantMessage: &knownpb.StringValue{},
+ }, {
+ desc: "StringValue",
+ inputMessage: &knownpb.StringValue{},
+ inputText: `"谷歌"`,
+ wantMessage: &knownpb.StringValue{Value: "谷歌"},
+ }, {
+ desc: "StringValue with invalid UTF8 error",
+ inputMessage: &knownpb.StringValue{},
+ inputText: "\"abc\xff\"",
+ wantMessage: &knownpb.StringValue{Value: "abc\xff"},
+ wantErr: true,
+ }, {
+ desc: "StringValue field with invalid UTF8 error",
+ inputMessage: &pb2.KnownTypes{},
+ inputText: "{\n \"optString\": \"abc\xff\"\n}",
+ wantMessage: &pb2.KnownTypes{
+ OptString: &knownpb.StringValue{Value: "abc\xff"},
+ },
+ wantErr: true,
+ }, {
+ desc: "BytesValue",
+ inputMessage: &knownpb.BytesValue{},
+ inputText: `"aGVsbG8="`,
+ wantMessage: &knownpb.BytesValue{Value: []byte("hello")},
+ }, {
+ desc: "Value null",
+ inputMessage: &knownpb.Value{},
+ inputText: `null`,
+ wantMessage: &knownpb.Value{Kind: &knownpb.Value_NullValue{}},
+ }, {
+ desc: "Value field null",
+ inputMessage: &pb2.KnownTypes{},
+ inputText: `{
+ "optValue": null
+}`,
+ wantMessage: &pb2.KnownTypes{
+ OptValue: &knownpb.Value{Kind: &knownpb.Value_NullValue{}},
+ },
+ }, {
+ desc: "Value bool",
+ inputMessage: &knownpb.Value{},
+ inputText: `false`,
+ wantMessage: &knownpb.Value{Kind: &knownpb.Value_BoolValue{}},
+ }, {
+ desc: "Value field bool",
+ inputMessage: &pb2.KnownTypes{},
+ inputText: `{
+ "optValue": true
+}`,
+ wantMessage: &pb2.KnownTypes{
+ OptValue: &knownpb.Value{Kind: &knownpb.Value_BoolValue{true}},
+ },
+ }, {
+ desc: "Value number",
+ inputMessage: &knownpb.Value{},
+ inputText: `1.02`,
+ wantMessage: &knownpb.Value{Kind: &knownpb.Value_NumberValue{1.02}},
+ }, {
+ desc: "Value field number",
+ inputMessage: &pb2.KnownTypes{},
+ inputText: `{
+ "optValue": 1.02
+}`,
+ wantMessage: &pb2.KnownTypes{
+ OptValue: &knownpb.Value{Kind: &knownpb.Value_NumberValue{1.02}},
+ },
+ }, {
+ desc: "Value string",
+ inputMessage: &knownpb.Value{},
+ inputText: `"hello"`,
+ wantMessage: &knownpb.Value{Kind: &knownpb.Value_StringValue{"hello"}},
+ }, {
+ desc: "Value string with invalid UTF8",
+ inputMessage: &knownpb.Value{},
+ inputText: "\"\xff\"",
+ wantMessage: &knownpb.Value{Kind: &knownpb.Value_StringValue{"\xff"}},
+ wantErr: true,
+ }, {
+ desc: "Value field string",
+ inputMessage: &pb2.KnownTypes{},
+ inputText: `{
+ "optValue": "NaN"
+}`,
+ wantMessage: &pb2.KnownTypes{
+ OptValue: &knownpb.Value{Kind: &knownpb.Value_StringValue{"NaN"}},
+ },
+ }, {
+ desc: "Value field string with invalid UTF8",
+ inputMessage: &pb2.KnownTypes{},
+ inputText: `{
+ "optValue": "` + "\xff" + `"
+}`,
+ wantMessage: &pb2.KnownTypes{
+ OptValue: &knownpb.Value{Kind: &knownpb.Value_StringValue{"\xff"}},
+ },
+ wantErr: true,
+ }, {
+ desc: "Value empty struct",
+ inputMessage: &knownpb.Value{},
+ inputText: `{}`,
+ wantMessage: &knownpb.Value{
+ Kind: &knownpb.Value_StructValue{
+ &knownpb.Struct{Fields: map[string]*knownpb.Value{}},
+ },
+ },
+ }, {
+ desc: "Value struct",
+ inputMessage: &knownpb.Value{},
+ inputText: `{
+ "string": "hello",
+ "number": 123,
+ "null": null,
+ "bool": false,
+ "struct": {
+ "string": "world"
+ },
+ "list": []
+}`,
+ wantMessage: &knownpb.Value{
+ Kind: &knownpb.Value_StructValue{
+ &knownpb.Struct{
+ Fields: map[string]*knownpb.Value{
+ "string": {Kind: &knownpb.Value_StringValue{"hello"}},
+ "number": {Kind: &knownpb.Value_NumberValue{123}},
+ "null": {Kind: &knownpb.Value_NullValue{}},
+ "bool": {Kind: &knownpb.Value_BoolValue{false}},
+ "struct": {
+ Kind: &knownpb.Value_StructValue{
+ &knownpb.Struct{
+ Fields: map[string]*knownpb.Value{
+ "string": {Kind: &knownpb.Value_StringValue{"world"}},
+ },
+ },
+ },
+ },
+ "list": {
+ Kind: &knownpb.Value_ListValue{&knownpb.ListValue{}},
+ },
+ },
+ },
+ },
+ },
+ }, {
+ desc: "Value struct with invalid UTF8 string",
+ inputMessage: &knownpb.Value{},
+ inputText: "{\"string\": \"abc\xff\"}",
+ wantMessage: &knownpb.Value{
+ Kind: &knownpb.Value_StructValue{
+ &knownpb.Struct{
+ Fields: map[string]*knownpb.Value{
+ "string": {Kind: &knownpb.Value_StringValue{"abc\xff"}},
+ },
+ },
+ },
+ },
+ wantErr: true,
+ }, {
+ desc: "Value field struct",
+ inputMessage: &pb2.KnownTypes{},
+ inputText: `{
+ "optValue": {
+ "string": "hello"
+ }
+}`,
+ wantMessage: &pb2.KnownTypes{
+ OptValue: &knownpb.Value{
+ Kind: &knownpb.Value_StructValue{
+ &knownpb.Struct{
+ Fields: map[string]*knownpb.Value{
+ "string": {Kind: &knownpb.Value_StringValue{"hello"}},
+ },
+ },
+ },
+ },
+ },
+ }, {
+ desc: "Value empty list",
+ inputMessage: &knownpb.Value{},
+ inputText: `[]`,
+ wantMessage: &knownpb.Value{
+ Kind: &knownpb.Value_ListValue{
+ &knownpb.ListValue{Values: []*knownpb.Value{}},
+ },
+ },
+ }, {
+ desc: "Value list",
+ inputMessage: &knownpb.Value{},
+ inputText: `[
+ "string",
+ 123,
+ null,
+ true,
+ {},
+ [
+ "string",
+ 1.23,
+ null,
+ false
+ ]
+]`,
+ wantMessage: &knownpb.Value{
+ Kind: &knownpb.Value_ListValue{
+ &knownpb.ListValue{
+ Values: []*knownpb.Value{
+ {Kind: &knownpb.Value_StringValue{"string"}},
+ {Kind: &knownpb.Value_NumberValue{123}},
+ {Kind: &knownpb.Value_NullValue{}},
+ {Kind: &knownpb.Value_BoolValue{true}},
+ {Kind: &knownpb.Value_StructValue{&knownpb.Struct{}}},
+ {
+ Kind: &knownpb.Value_ListValue{
+ &knownpb.ListValue{
+ Values: []*knownpb.Value{
+ {Kind: &knownpb.Value_StringValue{"string"}},
+ {Kind: &knownpb.Value_NumberValue{1.23}},
+ {Kind: &knownpb.Value_NullValue{}},
+ {Kind: &knownpb.Value_BoolValue{false}},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }, {
+ desc: "Value list with invalid UTF8 string",
+ inputMessage: &knownpb.Value{},
+ inputText: "[\"abc\xff\"]",
+ wantMessage: &knownpb.Value{
+ Kind: &knownpb.Value_ListValue{
+ &knownpb.ListValue{
+ Values: []*knownpb.Value{
+ {Kind: &knownpb.Value_StringValue{"abc\xff"}},
+ },
+ },
+ },
+ },
+ wantErr: true,
+ }, {
+ desc: "Value field list with invalid UTF8 string",
+ inputMessage: &pb2.KnownTypes{},
+ inputText: `{
+ "optValue": [ "` + "abc\xff" + `"]
+}`,
+ wantMessage: &pb2.KnownTypes{
+ OptValue: &knownpb.Value{
+ Kind: &knownpb.Value_ListValue{
+ &knownpb.ListValue{
+ Values: []*knownpb.Value{
+ {Kind: &knownpb.Value_StringValue{"abc\xff"}},
+ },
+ },
+ },
+ },
+ },
+ wantErr: true,
+ }, {
+ desc: "FieldMask empty",
+ inputMessage: &knownpb.FieldMask{},
+ inputText: `""`,
+ wantMessage: &knownpb.FieldMask{Paths: []string{}},
+ }, {
+ desc: "FieldMask",
+ inputMessage: &knownpb.FieldMask{},
+ inputText: `"foo,fooBar , foo.barQux ,Foo"`,
+ wantMessage: &knownpb.FieldMask{
+ Paths: []string{
+ "foo",
+ "foo_bar",
+ "foo.bar_qux",
+ "_foo",
+ },
+ },
+ }, {
+ desc: "FieldMask field",
+ inputMessage: &pb2.KnownTypes{},
+ inputText: `{
+ "optFieldmask": "foo, qux.fooBar"
+}`,
+ wantMessage: &pb2.KnownTypes{
+ OptFieldmask: &knownpb.FieldMask{
+ Paths: []string{
+ "foo",
+ "qux.foo_bar",
+ },
+ },
+ },
}}
for _, tt := range tests {
diff --git a/encoding/jsonpb/encode_test.go b/encoding/jsonpb/encode_test.go
index d6a2e62..09cb9f8 100644
--- a/encoding/jsonpb/encode_test.go
+++ b/encoding/jsonpb/encode_test.go
@@ -976,10 +976,18 @@
input: &knownpb.FloatValue{Value: 1.02},
want: `1.02`,
}, {
+ desc: "FloatValue Infinity",
+ input: &knownpb.FloatValue{Value: float32(math.Inf(-1))},
+ want: `"-Infinity"`,
+ }, {
desc: "DoubleValue",
input: &knownpb.DoubleValue{Value: 1.02},
want: `1.02`,
}, {
+ desc: "DoubleValue NaN",
+ input: &knownpb.DoubleValue{Value: math.NaN()},
+ want: `"NaN"`,
+ }, {
desc: "StringValue empty",
input: &knownpb.StringValue{},
want: `""`,
diff --git a/encoding/jsonpb/well_known_types.go b/encoding/jsonpb/well_known_types.go
index 101e4ba..1ff8854 100644
--- a/encoding/jsonpb/well_known_types.go
+++ b/encoding/jsonpb/well_known_types.go
@@ -9,6 +9,7 @@
"strings"
"time"
+ "github.com/golang/protobuf/v2/internal/encoding/json"
"github.com/golang/protobuf/v2/internal/errors"
"github.com/golang/protobuf/v2/internal/fieldnum"
"github.com/golang/protobuf/v2/proto"
@@ -79,7 +80,7 @@
return e.marshalFieldMask(m)
}
- panic(fmt.Sprintf("encoder.marshalCustomTypes(%q) does not have a custom marshaler", name))
+ panic(fmt.Sprintf("%q does not have a custom marshaler", name))
}
func (e encoder) marshalAny(m pref.Message) error {
@@ -329,3 +330,184 @@
func isASCIIUpper(c byte) bool {
return 'A' <= c && c <= 'Z'
}
+
+// unmarshalCustomType unmarshals given well-known type message that have
+// special JSON conversion rules. It needs to be a message type where
+// isCustomType returns true, else it will panic.
+func (d decoder) unmarshalCustomType(m pref.Message) error {
+ name := m.Type().FullName()
+ switch name {
+ case "google.protobuf.Any",
+ "google.protobuf.Duration",
+ "google.protobuf.Timestamp":
+ panic(fmt.Sprintf("unmarshaling of %v is not implemented yet", name))
+
+ case "google.protobuf.BoolValue",
+ "google.protobuf.DoubleValue",
+ "google.protobuf.FloatValue",
+ "google.protobuf.Int32Value",
+ "google.protobuf.Int64Value",
+ "google.protobuf.UInt32Value",
+ "google.protobuf.UInt64Value",
+ "google.protobuf.StringValue",
+ "google.protobuf.BytesValue":
+ return d.unmarshalKnownScalar(m)
+
+ case "google.protobuf.Struct":
+ return d.unmarshalStruct(m)
+
+ case "google.protobuf.ListValue":
+ return d.unmarshalListValue(m)
+
+ case "google.protobuf.Value":
+ return d.unmarshalKnownValue(m)
+
+ case "google.protobuf.FieldMask":
+ return d.unmarshalFieldMask(m)
+ }
+
+ panic(fmt.Sprintf("%q does not have a custom unmarshaler", name))
+}
+
+func (d decoder) unmarshalKnownScalar(m pref.Message) error {
+ var nerr errors.NonFatal
+ msgType := m.Type()
+ fieldDescs := msgType.Fields()
+ knownFields := m.KnownFields()
+
+ // The "value" field has the same field number for all wrapper types.
+ const num = fieldnum.BoolValue_Value
+ fd := fieldDescs.ByNumber(num)
+ val, err := d.unmarshalScalar(fd)
+ if !nerr.Merge(err) {
+ return err
+ }
+ knownFields.Set(num, val)
+ return nerr.E
+}
+
+func (d decoder) unmarshalStruct(m pref.Message) error {
+ msgType := m.Type()
+ fieldDescs := msgType.Fields()
+ knownFields := m.KnownFields()
+
+ fd := fieldDescs.ByNumber(fieldnum.Struct_Fields)
+ val := knownFields.Get(fieldnum.Struct_Fields)
+ return d.unmarshalMap(val.Map(), fd)
+}
+
+func (d decoder) unmarshalListValue(m pref.Message) error {
+ msgType := m.Type()
+ fieldDescs := msgType.Fields()
+ knownFields := m.KnownFields()
+
+ fd := fieldDescs.ByNumber(fieldnum.ListValue_Values)
+ val := knownFields.Get(fieldnum.ListValue_Values)
+ return d.unmarshalList(val.List(), fd)
+}
+
+func isKnownValue(fd pref.FieldDescriptor) bool {
+ md := fd.MessageType()
+ return md != nil && md.FullName() == "google.protobuf.Value"
+}
+
+func (d decoder) unmarshalKnownValue(m pref.Message) error {
+ var nerr errors.NonFatal
+ knownFields := m.KnownFields()
+
+ switch d.Peek() {
+ case json.Null:
+ d.Read()
+ knownFields.Set(fieldnum.Value_NullValue, pref.ValueOf(pref.EnumNumber(0)))
+
+ case json.Bool:
+ jval, err := d.Read()
+ if err != nil {
+ return err
+ }
+ val, err := unmarshalBool(jval)
+ if err != nil {
+ return err
+ }
+ knownFields.Set(fieldnum.Value_BoolValue, val)
+
+ case json.Number:
+ jval, err := d.Read()
+ if err != nil {
+ return err
+ }
+ val, err := unmarshalFloat(jval, 64)
+ if err != nil {
+ return err
+ }
+ knownFields.Set(fieldnum.Value_NumberValue, val)
+
+ case json.String:
+ // A JSON string may have been encoded from the number_value field,
+ // e.g. "NaN", "Infinity", etc. Parsing a proto double type also allows
+ // for it to be in JSON string form. Given this custom encoding spec,
+ // however, there is no way to identify that and hence a JSON string is
+ // always assigned to the string_value field, which means that certain
+ // encoding cannot be parsed back to the same field.
+ jval, err := d.Read()
+ if !nerr.Merge(err) {
+ return err
+ }
+ val, err := unmarshalString(jval)
+ if !nerr.Merge(err) {
+ return err
+ }
+ knownFields.Set(fieldnum.Value_StringValue, val)
+
+ case json.StartObject:
+ m := knownFields.NewMessage(fieldnum.Value_StructValue)
+ if err := d.unmarshalStruct(m); !nerr.Merge(err) {
+ return err
+ }
+ knownFields.Set(fieldnum.Value_StructValue, pref.ValueOf(m))
+
+ case json.StartArray:
+ m := knownFields.NewMessage(fieldnum.Value_ListValue)
+ if err := d.unmarshalListValue(m); !nerr.Merge(err) {
+ return err
+ }
+ knownFields.Set(fieldnum.Value_ListValue, pref.ValueOf(m))
+
+ default:
+ jval, err := d.Read()
+ if err != nil {
+ return err
+ }
+ return unexpectedJSONError{jval}
+ }
+
+ return nerr.E
+}
+
+func (d decoder) unmarshalFieldMask(m pref.Message) error {
+ var nerr errors.NonFatal
+ jval, err := d.Read()
+ if !nerr.Merge(err) {
+ return err
+ }
+ if jval.Type() != json.String {
+ return unexpectedJSONError{jval}
+ }
+ str := strings.TrimSpace(jval.String())
+ if str == "" {
+ return nil
+ }
+ paths := strings.Split(str, ",")
+
+ knownFields := m.KnownFields()
+ val := knownFields.Get(fieldnum.FieldMask_Paths)
+ list := val.List()
+
+ for _, s := range paths {
+ s = strings.TrimSpace(s)
+ // Convert to snake_case. Unlike encoding, no validation is done because
+ // it is not possible to know the original path names.
+ list.Append(pref.ValueOf(snakeCase(s)))
+ }
+ return nil
+}