encoding/protojson: refactor to follow prototext pattern
All unmarshaling error messages now contain line number and column
information, except for the following errors:
- `unexpected EOF`
- `no support for proto1 MessageSets`
- `required fields X not set`
Changes to internal/encoding/json:
- Moved encoding funcs in string.go and number.go into encode.go.
- Separated out encoding kind constants from decoding ones.
- Renamed file string.go to decode_string.go.
- Renamed file number.go to decode_number.go.
- Renamed Type struct to Kind.
- Renamed Value struct to Token.
- Token accessor methods no longer return error.
Name, Bool, ParsedString will panic if called on the wrong kind.
Float, Int, Uint has ok bool result to check against.
- Changed Peek to return Token and error.
Changes to encoding/protojson:
- Updated internal/encoding/json API calls.
- Added line info on most unmarshaling error messages and kept
description simple and consistent.
Change-Id: Ie50456694f2214c5c4fafd2c9b9239680da0deec
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/218978
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/encoding/protojson/decode.go b/encoding/protojson/decode.go
index db8a3df..4d13d77 100644
--- a/encoding/protojson/decode.go
+++ b/encoding/protojson/decode.go
@@ -45,8 +45,6 @@
protoregistry.MessageTypeResolver
protoregistry.ExtensionTypeResolver
}
-
- decoder *json.Decoder
}
// Unmarshal reads the given []byte and populates the given proto.Message using
@@ -59,19 +57,19 @@
if o.Resolver == nil {
o.Resolver = protoregistry.GlobalTypes
}
- o.decoder = json.NewDecoder(b)
- if err := o.unmarshalMessage(m.ProtoReflect(), false); err != nil {
+ dec := decoder{json.NewDecoder(b), o}
+ if err := dec.unmarshalMessage(m.ProtoReflect(), false); err != nil {
return err
}
// Check for EOF.
- val, err := o.decoder.Read()
+ tok, err := dec.Read()
if err != nil {
return err
}
- if val.Type() != json.EOF {
- return unexpectedJSONError{val}
+ if tok.Kind() != json.EOF {
+ return dec.unexpectedTokenError(tok)
}
if o.AllowPartial {
@@ -80,53 +78,45 @@
return proto.IsInitialized(m)
}
-// unexpectedJSONError is an error that contains the unexpected json.Value. This
-// is returned by methods to provide callers the read json.Value that it did not
-// expect.
-// TODO: Consider moving this to internal/encoding/json for consistency with
-// errors that package returns.
-type unexpectedJSONError struct {
- value json.Value
+type decoder struct {
+ *json.Decoder
+ opts UnmarshalOptions
}
-func (e unexpectedJSONError) Error() string {
- return newError("unexpected value %s", e.value).Error()
+// newError returns an error object with position info.
+func (d decoder) newError(pos int, f string, x ...interface{}) error {
+ line, column := d.Position(pos)
+ head := fmt.Sprintf("(line %d:%d): ", line, column)
+ return errors.New(head+f, x...)
}
-// newError returns an error object. If one of the values passed in is of
-// json.Value type, it produces an error with position info.
-func newError(f string, x ...interface{}) error {
- var hasValue bool
- var line, column int
- for i := 0; i < len(x); i++ {
- if val, ok := x[i].(json.Value); ok {
- line, column = val.Position()
- hasValue = true
- break
- }
- }
- e := errors.New(f, x...)
- if hasValue {
- return errors.New("(line %d:%d): %v", line, column, e)
- }
- return e
+// unexpectedTokenError returns a syntax error for the given unexpected token.
+func (d decoder) unexpectedTokenError(tok json.Token) error {
+ return d.syntaxError(tok.Pos(), "unexpected token %s", tok.RawString())
+}
+
+// syntaxError returns a syntax error for given position.
+func (d decoder) syntaxError(pos int, f string, x ...interface{}) error {
+ line, column := d.Position(pos)
+ head := fmt.Sprintf("syntax error (line %d:%d): ", line, column)
+ return errors.New(head+f, x...)
}
// unmarshalMessage unmarshals a message into the given protoreflect.Message.
-func (o UnmarshalOptions) unmarshalMessage(m pref.Message, skipTypeURL bool) error {
+func (d decoder) unmarshalMessage(m pref.Message, skipTypeURL bool) error {
if isCustomType(m.Descriptor().FullName()) {
- return o.unmarshalCustomType(m)
+ return d.unmarshalCustomType(m)
}
- jval, err := o.decoder.Read()
+ tok, err := d.Read()
if err != nil {
return err
}
- if jval.Type() != json.StartObject {
- return unexpectedJSONError{jval}
+ if tok.Kind() != json.ObjectOpen {
+ return d.unexpectedTokenError(tok)
}
- if err := o.unmarshalFields(m, skipTypeURL); err != nil {
+ if err := d.unmarshalFields(m, skipTypeURL); err != nil {
return err
}
@@ -134,7 +124,7 @@
}
// unmarshalFields unmarshals the fields into the given protoreflect.Message.
-func (o UnmarshalOptions) unmarshalFields(m pref.Message, skipTypeURL bool) error {
+func (d decoder) unmarshalFields(m pref.Message, skipTypeURL bool) error {
messageDesc := m.Descriptor()
if !flags.ProtoLegacy && messageset.IsMessageSet(messageDesc) {
return errors.New("no support for proto1 MessageSets")
@@ -145,28 +135,25 @@
fieldDescs := messageDesc.Fields()
for {
// Read field name.
- jval, err := o.decoder.Read()
+ tok, err := d.Read()
if err != nil {
return err
}
- switch jval.Type() {
+ switch tok.Kind() {
default:
- return unexpectedJSONError{jval}
- case json.EndObject:
+ return d.unexpectedTokenError(tok)
+ case json.ObjectClose:
return nil
case json.Name:
// Continue below.
}
- name, err := jval.Name()
- if err != nil {
- return err
- }
+ name := tok.Name()
// Unmarshaling a non-custom embedded message in Any will contain the
// JSON field "@type" which should be skipped because it is not a field
// of the embedded message, but simply an artifact of the Any format.
if skipTypeURL && name == "@type" {
- o.decoder.Read()
+ d.Read()
continue
}
@@ -175,14 +162,14 @@
if strings.HasPrefix(name, "[") && strings.HasSuffix(name, "]") {
// Only extension names are in [name] format.
extName := pref.FullName(name[1 : len(name)-1])
- extType, err := o.findExtension(extName)
+ extType, err := d.findExtension(extName)
if err != nil && err != protoregistry.NotFound {
- return errors.New("unable to resolve [%v]: %v", extName, err)
+ return d.newError(tok.Pos(), "unable to resolve %s: %v", tok.RawString(), err)
}
if extType != nil {
fd = extType.TypeDescriptor()
if !messageDesc.ExtensionRanges().Has(fd.Number()) || fd.ContainingMessage().FullName() != messageDesc.FullName() {
- return errors.New("message %v cannot be extended by %v", messageDesc.FullName(), fd.FullName())
+ return d.newError(tok.Pos(), "message %v cannot be extended by %v", messageDesc.FullName(), fd.FullName())
}
}
} else {
@@ -210,65 +197,65 @@
if fd == nil {
// Field is unknown.
- if o.DiscardUnknown {
- if err := skipJSONValue(o.decoder); err != nil {
+ if d.opts.DiscardUnknown {
+ if err := d.skipJSONValue(); err != nil {
return err
}
continue
}
- return newError("%v contains unknown field %s", messageDesc.FullName(), jval)
+ return d.newError(tok.Pos(), "unknown field %v", tok.RawString())
}
// Do not allow duplicate fields.
num := uint64(fd.Number())
if seenNums.Has(num) {
- return newError("%v contains repeated field %s", messageDesc.FullName(), jval)
+ return d.newError(tok.Pos(), "duplicate field %v", tok.RawString())
}
seenNums.Set(num)
// No need to set values for JSON null unless the field type is
// google.protobuf.Value or google.protobuf.NullValue.
- if o.decoder.Peek() == json.Null && !isKnownValue(fd) && !isNullValue(fd) {
- o.decoder.Read()
+ if tok, _ := d.Peek(); tok.Kind() == json.Null && !isKnownValue(fd) && !isNullValue(fd) {
+ d.Read()
continue
}
switch {
case fd.IsList():
list := m.Mutable(fd).List()
- if err := o.unmarshalList(list, fd); err != nil {
- return errors.New("%v|%q: %v", fd.FullName(), name, err)
+ if err := d.unmarshalList(list, fd); err != nil {
+ return err
}
case fd.IsMap():
mmap := m.Mutable(fd).Map()
- if err := o.unmarshalMap(mmap, fd); err != nil {
- return errors.New("%v|%q: %v", fd.FullName(), name, err)
+ if err := d.unmarshalMap(mmap, fd); err != nil {
+ return err
}
default:
// If field is a oneof, check if it has already been set.
if od := fd.ContainingOneof(); od != nil {
idx := uint64(od.Index())
if seenOneofs.Has(idx) {
- return errors.New("%v: oneof is already set", od.FullName())
+ return d.newError(tok.Pos(), "error parsing %s, oneof %v is already set", tok.RawString(), od.FullName())
}
seenOneofs.Set(idx)
}
// Required or optional fields.
- if err := o.unmarshalSingular(m, fd); err != nil {
- return errors.New("%v|%q: %v", fd.FullName(), name, err)
+ if err := d.unmarshalSingular(m, fd); err != nil {
+ return err
}
}
}
}
// findExtension returns protoreflect.ExtensionType from the resolver if found.
-func (o UnmarshalOptions) findExtension(xtName pref.FullName) (pref.ExtensionType, error) {
- xt, err := o.Resolver.FindExtensionByName(xtName)
+func (d decoder) findExtension(xtName pref.FullName) (pref.ExtensionType, error) {
+ xt, err := d.opts.Resolver.FindExtensionByName(xtName)
if err == nil {
return xt, nil
}
- return messageset.FindMessageSetExtension(o.Resolver, xtName)
+ return messageset.FindMessageSetExtension(d.opts.Resolver, xtName)
}
func isKnownValue(fd pref.FieldDescriptor) bool {
@@ -281,17 +268,17 @@
return ed != nil && ed.FullName() == "google.protobuf.NullValue"
}
-// unmarshalSingular unmarshals to the non-repeated field specified by the given
-// FieldDescriptor.
-func (o UnmarshalOptions) unmarshalSingular(m pref.Message, fd pref.FieldDescriptor) error {
+// unmarshalSingular unmarshals to the non-repeated field specified
+// by the given FieldDescriptor.
+func (d decoder) unmarshalSingular(m pref.Message, fd pref.FieldDescriptor) error {
var val pref.Value
var err error
switch fd.Kind() {
case pref.MessageKind, pref.GroupKind:
val = m.NewField(fd)
- err = o.unmarshalMessage(val.Message(), false)
+ err = d.unmarshalMessage(val.Message(), false)
default:
- val, err = o.unmarshalScalar(fd)
+ val, err = d.unmarshalScalar(fd)
}
if err != nil {
@@ -303,11 +290,11 @@
// unmarshalScalar unmarshals to a scalar/enum protoreflect.Value specified by
// the given FieldDescriptor.
-func (o UnmarshalOptions) unmarshalScalar(fd pref.FieldDescriptor) (pref.Value, error) {
+func (d decoder) unmarshalScalar(fd pref.FieldDescriptor) (pref.Value, error) {
const b32 int = 32
const b64 int = 64
- jval, err := o.decoder.Read()
+ tok, err := d.Read()
if err != nil {
return pref.Value{}, err
}
@@ -315,177 +302,182 @@
kind := fd.Kind()
switch kind {
case pref.BoolKind:
- return unmarshalBool(jval)
+ if tok.Kind() == json.Bool {
+ return pref.ValueOfBool(tok.Bool()), nil
+ }
case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind:
- return unmarshalInt(jval, b32)
+ if v, ok := unmarshalInt(tok, b32); ok {
+ return v, nil
+ }
case pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
- return unmarshalInt(jval, b64)
+ if v, ok := unmarshalInt(tok, b64); ok {
+ return v, nil
+ }
case pref.Uint32Kind, pref.Fixed32Kind:
- return unmarshalUint(jval, b32)
+ if v, ok := unmarshalUint(tok, b32); ok {
+ return v, nil
+ }
case pref.Uint64Kind, pref.Fixed64Kind:
- return unmarshalUint(jval, b64)
+ if v, ok := unmarshalUint(tok, b64); ok {
+ return v, nil
+ }
case pref.FloatKind:
- return unmarshalFloat(jval, b32)
+ if v, ok := unmarshalFloat(tok, b32); ok {
+ return v, nil
+ }
case pref.DoubleKind:
- return unmarshalFloat(jval, b64)
+ if v, ok := unmarshalFloat(tok, b64); ok {
+ return v, nil
+ }
case pref.StringKind:
- pval, err := unmarshalString(jval)
- if err != nil {
- return pval, err
+ if tok.Kind() == json.String {
+ return pref.ValueOfString(tok.ParsedString()), nil
}
- return pval, nil
case pref.BytesKind:
- return unmarshalBytes(jval)
+ if v, ok := unmarshalBytes(tok); ok {
+ return v, nil
+ }
case pref.EnumKind:
- return unmarshalEnum(jval, fd)
+ if v, ok := unmarshalEnum(tok, fd); ok {
+ return v, nil
+ }
+
+ default:
+ panic(fmt.Sprintf("unmarshalScalar: invalid scalar kind %v", kind))
}
- panic(fmt.Sprintf("invalid scalar kind %v", kind))
+ return pref.Value{}, d.newError(tok.Pos(), "invalid value for %v type: %v", kind, tok.RawString())
}
-func unmarshalBool(jval json.Value) (pref.Value, error) {
- if jval.Type() != json.Bool {
- return pref.Value{}, unexpectedJSONError{jval}
- }
- b, err := jval.Bool()
- return pref.ValueOfBool(b), err
-}
-
-func unmarshalInt(jval json.Value, bitSize int) (pref.Value, error) {
- switch jval.Type() {
+func unmarshalInt(tok json.Token, bitSize int) (pref.Value, bool) {
+ switch tok.Kind() {
case json.Number:
- return getInt(jval, bitSize)
+ return getInt(tok, bitSize)
case json.String:
// Decode number from string.
- s := strings.TrimSpace(jval.String())
- if len(s) != len(jval.String()) {
- return pref.Value{}, errors.New("invalid number %v", jval.Raw())
+ s := strings.TrimSpace(tok.ParsedString())
+ if len(s) != len(tok.ParsedString()) {
+ return pref.Value{}, false
}
dec := json.NewDecoder([]byte(s))
- jval, err := dec.Read()
+ tok, err := dec.Read()
if err != nil {
- return pref.Value{}, err
+ return pref.Value{}, false
}
- return getInt(jval, bitSize)
+ return getInt(tok, bitSize)
}
- return pref.Value{}, unexpectedJSONError{jval}
+ return pref.Value{}, false
}
-func getInt(jval json.Value, bitSize int) (pref.Value, error) {
- n, err := jval.Int(bitSize)
- if err != nil {
- return pref.Value{}, err
+func getInt(tok json.Token, bitSize int) (pref.Value, bool) {
+ n, ok := tok.Int(bitSize)
+ if !ok {
+ return pref.Value{}, false
}
if bitSize == 32 {
- return pref.ValueOfInt32(int32(n)), nil
+ return pref.ValueOfInt32(int32(n)), true
}
- return pref.ValueOfInt64(n), nil
+ return pref.ValueOfInt64(n), true
}
-func unmarshalUint(jval json.Value, bitSize int) (pref.Value, error) {
- switch jval.Type() {
+func unmarshalUint(tok json.Token, bitSize int) (pref.Value, bool) {
+ switch tok.Kind() {
case json.Number:
- return getUint(jval, bitSize)
+ return getUint(tok, bitSize)
case json.String:
// Decode number from string.
- s := strings.TrimSpace(jval.String())
- if len(s) != len(jval.String()) {
- return pref.Value{}, errors.New("invalid number %v", jval.Raw())
+ s := strings.TrimSpace(tok.ParsedString())
+ if len(s) != len(tok.ParsedString()) {
+ return pref.Value{}, false
}
dec := json.NewDecoder([]byte(s))
- jval, err := dec.Read()
+ tok, err := dec.Read()
if err != nil {
- return pref.Value{}, err
+ return pref.Value{}, false
}
- return getUint(jval, bitSize)
+ return getUint(tok, bitSize)
}
- return pref.Value{}, unexpectedJSONError{jval}
+ return pref.Value{}, false
}
-func getUint(jval json.Value, bitSize int) (pref.Value, error) {
- n, err := jval.Uint(bitSize)
- if err != nil {
- return pref.Value{}, err
+func getUint(tok json.Token, bitSize int) (pref.Value, bool) {
+ n, ok := tok.Uint(bitSize)
+ if !ok {
+ return pref.Value{}, false
}
if bitSize == 32 {
- return pref.ValueOfUint32(uint32(n)), nil
+ return pref.ValueOfUint32(uint32(n)), true
}
- return pref.ValueOfUint64(n), nil
+ return pref.ValueOfUint64(n), true
}
-func unmarshalFloat(jval json.Value, bitSize int) (pref.Value, error) {
- switch jval.Type() {
+func unmarshalFloat(tok json.Token, bitSize int) (pref.Value, bool) {
+ switch tok.Kind() {
case json.Number:
- return getFloat(jval, bitSize)
+ return getFloat(tok, bitSize)
case json.String:
- s := jval.String()
+ s := tok.ParsedString()
switch s {
case "NaN":
if bitSize == 32 {
- return pref.ValueOfFloat32(float32(math.NaN())), nil
+ return pref.ValueOfFloat32(float32(math.NaN())), true
}
- return pref.ValueOfFloat64(math.NaN()), nil
+ return pref.ValueOfFloat64(math.NaN()), true
case "Infinity":
if bitSize == 32 {
- return pref.ValueOfFloat32(float32(math.Inf(+1))), nil
+ return pref.ValueOfFloat32(float32(math.Inf(+1))), true
}
- return pref.ValueOfFloat64(math.Inf(+1)), nil
+ return pref.ValueOfFloat64(math.Inf(+1)), true
case "-Infinity":
if bitSize == 32 {
- return pref.ValueOfFloat32(float32(math.Inf(-1))), nil
+ return pref.ValueOfFloat32(float32(math.Inf(-1))), true
}
- return pref.ValueOfFloat64(math.Inf(-1)), nil
+ return pref.ValueOfFloat64(math.Inf(-1)), true
}
+
// Decode number from string.
if len(s) != len(strings.TrimSpace(s)) {
- return pref.Value{}, errors.New("invalid number %v", jval.Raw())
+ return pref.Value{}, false
}
dec := json.NewDecoder([]byte(s))
- jval, err := dec.Read()
+ tok, err := dec.Read()
if err != nil {
- return pref.Value{}, err
+ return pref.Value{}, false
}
- return getFloat(jval, bitSize)
+ return getFloat(tok, bitSize)
}
- return pref.Value{}, unexpectedJSONError{jval}
+ return pref.Value{}, false
}
-func getFloat(jval json.Value, bitSize int) (pref.Value, error) {
- n, err := jval.Float(bitSize)
- if err != nil {
- return pref.Value{}, err
+func getFloat(tok json.Token, bitSize int) (pref.Value, bool) {
+ n, ok := tok.Float(bitSize)
+ if !ok {
+ return pref.Value{}, false
}
if bitSize == 32 {
- return pref.ValueOfFloat32(float32(n)), nil
+ return pref.ValueOfFloat32(float32(n)), true
}
- return pref.ValueOfFloat64(n), nil
+ return pref.ValueOfFloat64(n), true
}
-func unmarshalString(jval json.Value) (pref.Value, error) {
- if jval.Type() != json.String {
- return pref.Value{}, unexpectedJSONError{jval}
- }
- return pref.ValueOfString(jval.String()), nil
-}
-
-func unmarshalBytes(jval json.Value) (pref.Value, error) {
- if jval.Type() != json.String {
- return pref.Value{}, unexpectedJSONError{jval}
+func unmarshalBytes(tok json.Token) (pref.Value, bool) {
+ if tok.Kind() != json.String {
+ return pref.Value{}, false
}
- s := jval.String()
+ s := tok.ParsedString()
enc := base64.StdEncoding
if strings.ContainsAny(s, "-_") {
enc = base64.URLEncoding
@@ -495,88 +487,93 @@
}
b, err := enc.DecodeString(s)
if err != nil {
- return pref.Value{}, err
+ return pref.Value{}, false
}
- return pref.ValueOfBytes(b), nil
+ return pref.ValueOfBytes(b), true
}
-func unmarshalEnum(jval json.Value, fd pref.FieldDescriptor) (pref.Value, error) {
- switch jval.Type() {
+func unmarshalEnum(tok json.Token, fd pref.FieldDescriptor) (pref.Value, bool) {
+ switch tok.Kind() {
case json.String:
// Lookup EnumNumber based on name.
- s := jval.String()
+ s := tok.ParsedString()
if enumVal := fd.Enum().Values().ByName(pref.Name(s)); enumVal != nil {
- return pref.ValueOfEnum(enumVal.Number()), nil
+ return pref.ValueOfEnum(enumVal.Number()), true
}
- return pref.Value{}, newError("invalid enum value %q", jval)
case json.Number:
- n, err := jval.Int(32)
- if err != nil {
- return pref.Value{}, err
+ if n, ok := tok.Int(32); ok {
+ return pref.ValueOfEnum(pref.EnumNumber(n)), true
}
- return pref.ValueOfEnum(pref.EnumNumber(n)), nil
case json.Null:
// This is only valid for google.protobuf.NullValue.
if isNullValue(fd) {
- return pref.ValueOfEnum(0), nil
+ return pref.ValueOfEnum(0), true
}
}
- return pref.Value{}, unexpectedJSONError{jval}
+ return pref.Value{}, false
}
-func (o UnmarshalOptions) unmarshalList(list pref.List, fd pref.FieldDescriptor) error {
- jval, err := o.decoder.Read()
+func (d decoder) unmarshalList(list pref.List, fd pref.FieldDescriptor) error {
+ tok, err := d.Read()
if err != nil {
return err
}
- if jval.Type() != json.StartArray {
- return unexpectedJSONError{jval}
+ if tok.Kind() != json.ArrayOpen {
+ return d.unexpectedTokenError(tok)
}
switch fd.Kind() {
case pref.MessageKind, pref.GroupKind:
for {
- val := list.NewElement()
- err := o.unmarshalMessage(val.Message(), false)
+ tok, err := d.Peek()
if err != nil {
- if e, ok := err.(unexpectedJSONError); ok {
- if e.value.Type() == json.EndArray {
- // Done with list.
- return nil
- }
- }
+ return err
+ }
+
+ if tok.Kind() == json.ArrayClose {
+ d.Read()
+ return nil
+ }
+
+ val := list.NewElement()
+ if err := d.unmarshalMessage(val.Message(), false); err != nil {
return err
}
list.Append(val)
}
default:
for {
- val, err := o.unmarshalScalar(fd)
+ tok, err := d.Peek()
if err != nil {
- if e, ok := err.(unexpectedJSONError); ok {
- if e.value.Type() == json.EndArray {
- // Done with list.
- return nil
- }
- }
+ return err
+ }
+
+ if tok.Kind() == json.ArrayClose {
+ d.Read()
+ return nil
+ }
+
+ val, err := d.unmarshalScalar(fd)
+ if err != nil {
return err
}
list.Append(val)
}
}
+
return nil
}
-func (o UnmarshalOptions) unmarshalMap(mmap pref.Map, fd pref.FieldDescriptor) error {
- jval, err := o.decoder.Read()
+func (d decoder) unmarshalMap(mmap pref.Map, fd pref.FieldDescriptor) error {
+ tok, err := d.Read()
if err != nil {
return err
}
- if jval.Type() != json.StartObject {
- return unexpectedJSONError{jval}
+ if tok.Kind() != json.ObjectOpen {
+ return d.unexpectedTokenError(tok)
}
// Determine ahead whether map entry is a scalar type or a message type in
@@ -587,47 +584,42 @@
case pref.MessageKind, pref.GroupKind:
unmarshalMapValue = func() (pref.Value, error) {
val := mmap.NewValue()
- if err := o.unmarshalMessage(val.Message(), false); err != nil {
+ if err := d.unmarshalMessage(val.Message(), false); err != nil {
return pref.Value{}, err
}
return val, nil
}
default:
unmarshalMapValue = func() (pref.Value, error) {
- return o.unmarshalScalar(fd.MapValue())
+ return d.unmarshalScalar(fd.MapValue())
}
}
Loop:
for {
// Read field name.
- jval, err := o.decoder.Read()
+ tok, err := d.Read()
if err != nil {
return err
}
- switch jval.Type() {
+ switch tok.Kind() {
default:
- return unexpectedJSONError{jval}
- case json.EndObject:
+ return d.unexpectedTokenError(tok)
+ case json.ObjectClose:
break Loop
case json.Name:
// Continue.
}
- name, err := jval.Name()
- if err != nil {
- return err
- }
-
// Unmarshal field name.
- pkey, err := unmarshalMapKey(name, fd.MapKey())
+ pkey, err := d.unmarshalMapKey(tok, fd.MapKey())
if err != nil {
return err
}
// Check for duplicate field name.
if mmap.Has(pkey) {
- return newError("duplicate map key %q", jval)
+ return d.newError(tok.Pos(), "duplicate map key %v", tok.RawString())
}
// Read and unmarshal field value.
@@ -642,13 +634,14 @@
return nil
}
-// unmarshalMapKey converts given string into a protoreflect.MapKey. A map key type is any
-// integral or string type.
-func unmarshalMapKey(name string, fd pref.FieldDescriptor) (pref.MapKey, error) {
+// unmarshalMapKey converts given token of Name kind into a protoreflect.MapKey.
+// A map key type is any integral or string type.
+func (d decoder) unmarshalMapKey(tok json.Token, fd pref.FieldDescriptor) (pref.MapKey, error) {
const b32 = 32
const b64 = 64
const base10 = 10
+ name := tok.Name()
kind := fd.Kind()
switch kind {
case pref.StringKind:
@@ -661,36 +654,30 @@
case "false":
return pref.ValueOfBool(false).MapKey(), nil
}
- return pref.MapKey{}, errors.New("invalid value for boolean key %q", name)
case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind:
- n, err := strconv.ParseInt(name, base10, b32)
- if err != nil {
- return pref.MapKey{}, err
+ if n, err := strconv.ParseInt(name, base10, b32); err == nil {
+ return pref.ValueOfInt32(int32(n)).MapKey(), nil
}
- return pref.ValueOfInt32(int32(n)).MapKey(), nil
case pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
- n, err := strconv.ParseInt(name, base10, b64)
- if err != nil {
- return pref.MapKey{}, err
+ if n, err := strconv.ParseInt(name, base10, b64); err == nil {
+ return pref.ValueOfInt64(int64(n)).MapKey(), nil
}
- return pref.ValueOfInt64(int64(n)).MapKey(), nil
case pref.Uint32Kind, pref.Fixed32Kind:
- n, err := strconv.ParseUint(name, base10, b32)
- if err != nil {
- return pref.MapKey{}, err
+ if n, err := strconv.ParseUint(name, base10, b32); err == nil {
+ return pref.ValueOfUint32(uint32(n)).MapKey(), nil
}
- return pref.ValueOfUint32(uint32(n)).MapKey(), nil
case pref.Uint64Kind, pref.Fixed64Kind:
- n, err := strconv.ParseUint(name, base10, b64)
- if err != nil {
- return pref.MapKey{}, err
+ if n, err := strconv.ParseUint(name, base10, b64); err == nil {
+ return pref.ValueOfUint64(uint64(n)).MapKey(), nil
}
- return pref.ValueOfUint64(uint64(n)).MapKey(), nil
+
+ default:
+ panic(fmt.Sprintf("invalid kind for map key: %v", kind))
}
- panic(fmt.Sprintf("%s: invalid kind %s for map key", fd.FullName(), kind))
+ return pref.MapKey{}, d.newError(tok.Pos(), "invalid value for %v key: %s", kind, tok.RawString())
}
diff --git a/encoding/protojson/decode_test.go b/encoding/protojson/decode_test.go
index e37a9a3..5ef4594 100644
--- a/encoding/protojson/decode_test.go
+++ b/encoding/protojson/decode_test.go
@@ -6,9 +6,11 @@
import (
"math"
+ "strings"
"testing"
"google.golang.org/protobuf/encoding/protojson"
+ "google.golang.org/protobuf/internal/errors"
"google.golang.org/protobuf/internal/flags"
"google.golang.org/protobuf/proto"
preg "google.golang.org/protobuf/reflect/protoregistry"
@@ -33,9 +35,8 @@
inputMessage proto.Message
inputText string
wantMessage proto.Message
- // TODO: verify expected error message substring.
- wantErr bool
- skip bool
+ wantErr string // Expected error substring.
+ skip bool
}{{
desc: "proto2 empty message",
inputMessage: &pb2.Scalars{},
@@ -45,7 +46,7 @@
desc: "unexpected value instead of EOF",
inputMessage: &pb2.Scalars{},
inputText: "{} {}",
- wantErr: true,
+ wantErr: `(line 1:4): unexpected token {`,
}, {
desc: "proto2 optional scalars set to zero values",
inputMessage: &pb2.Scalars{},
@@ -157,7 +158,7 @@
desc: "not boolean",
inputMessage: &pb3.Scalars{},
inputText: `{"sBool": "true"}`,
- wantErr: true,
+ wantErr: `invalid value for bool type: "true"`,
}, {
desc: "float and double",
inputMessage: &pb3.Scalars{},
@@ -206,22 +207,22 @@
desc: "float exceeds limit",
inputMessage: &pb3.Scalars{},
inputText: `{"sFloat": 3.4e39}`,
- wantErr: true,
+ wantErr: `invalid value for float type: 3.4e39`,
}, {
desc: "float in string exceeds limit",
inputMessage: &pb3.Scalars{},
inputText: `{"sFloat": "-3.4e39"}`,
- wantErr: true,
+ wantErr: `invalid value for float type: "-3.4e39"`,
}, {
desc: "double exceeds limit",
inputMessage: &pb3.Scalars{},
- inputText: `{"sFloat": -1.79e+309}`,
- wantErr: true,
+ inputText: `{"sDouble": -1.79e+309}`,
+ wantErr: `invalid value for double type: -1.79e+309`,
}, {
desc: "double in string exceeds limit",
inputMessage: &pb3.Scalars{},
- inputText: `{"sFloat": "1.79e+309"}`,
- wantErr: true,
+ inputText: `{"sDouble": "1.79e+309"}`,
+ wantErr: `invalid value for double type: "1.79e+309"`,
}, {
desc: "infinites",
inputMessage: &pb3.Scalars{},
@@ -234,22 +235,22 @@
desc: "float string with leading space",
inputMessage: &pb3.Scalars{},
inputText: `{"sFloat": " 1.234"}`,
- wantErr: true,
+ wantErr: `invalid value for float type: " 1.234"`,
}, {
desc: "double string with trailing space",
inputMessage: &pb3.Scalars{},
inputText: `{"sDouble": "5.678 "}`,
- wantErr: true,
+ wantErr: `invalid value for double type: "5.678 "`,
}, {
desc: "not float",
inputMessage: &pb3.Scalars{},
inputText: `{"sFloat": true}`,
- wantErr: true,
+ wantErr: `invalid value for float type: true`,
}, {
desc: "not double",
inputMessage: &pb3.Scalars{},
inputText: `{"sDouble": "not a number"}`,
- wantErr: true,
+ wantErr: `invalid value for double type: "not a number"`,
}, {
desc: "integers",
inputMessage: &pb3.Scalars{},
@@ -315,42 +316,42 @@
desc: "integer string with leading space",
inputMessage: &pb3.Scalars{},
inputText: `{"sInt32": " 1234"}`,
- wantErr: true,
+ wantErr: `invalid value for int32 type: " 1234"`,
}, {
desc: "integer string with trailing space",
inputMessage: &pb3.Scalars{},
inputText: `{"sUint32": "1e2 "}`,
- wantErr: true,
+ wantErr: `invalid value for uint32 type: "1e2 "`,
}, {
desc: "number is not an integer",
inputMessage: &pb3.Scalars{},
inputText: `{"sInt32": 1.001}`,
- wantErr: true,
+ wantErr: `invalid value for int32 type: 1.001`,
}, {
desc: "32-bit int exceeds limit",
inputMessage: &pb3.Scalars{},
inputText: `{"sInt32": 2e10}`,
- wantErr: true,
+ wantErr: `invalid value for int32 type: 2e10`,
}, {
desc: "64-bit int exceeds limit",
inputMessage: &pb3.Scalars{},
inputText: `{"sSfixed64": -9e19}`,
- wantErr: true,
+ wantErr: `invalid value for sfixed64 type: -9e19`,
}, {
desc: "not integer",
inputMessage: &pb3.Scalars{},
inputText: `{"sInt32": "not a number"}`,
- wantErr: true,
+ wantErr: `invalid value for int32 type: "not a number"`,
}, {
desc: "not unsigned integer",
inputMessage: &pb3.Scalars{},
inputText: `{"sUint32": "not a number"}`,
- wantErr: true,
+ wantErr: `invalid value for uint32 type: "not a number"`,
}, {
desc: "number is not an unsigned integer",
inputMessage: &pb3.Scalars{},
inputText: `{"sUint32": -1}`,
- wantErr: true,
+ wantErr: `invalid value for uint32 type: -1`,
}, {
desc: "string",
inputMessage: &pb2.Scalars{},
@@ -362,12 +363,12 @@
desc: "string with invalid UTF-8",
inputMessage: &pb3.Scalars{},
inputText: "{\"sString\": \"\xff\"}",
- wantErr: true,
+ wantErr: `(line 1:13): invalid UTF-8 in string`,
}, {
desc: "not string",
inputMessage: &pb2.Scalars{},
inputText: `{"optString": 42}`,
- wantErr: true,
+ wantErr: `invalid value for string type: 42`,
}, {
desc: "bytes",
inputMessage: &pb3.Scalars{},
@@ -386,7 +387,7 @@
desc: "not bytes",
inputMessage: &pb3.Scalars{},
inputText: `{"sBytes": true}`,
- wantErr: true,
+ wantErr: `invalid value for bytes type: true`,
}, {
desc: "proto2 enum",
inputMessage: &pb2.Enums{},
@@ -437,21 +438,21 @@
inputText: `{
"sEnum": "1"
}`,
- wantErr: true,
+ wantErr: `invalid value for enum type: "1"`,
}, {
desc: "enum set to invalid named",
inputMessage: &pb3.Enums{},
inputText: `{
"sEnum": "UNNAMED"
}`,
- wantErr: true,
+ wantErr: `invalid value for enum type: "UNNAMED"`,
}, {
desc: "enum set to not enum",
inputMessage: &pb3.Enums{},
inputText: `{
"sEnum": true
}`,
- wantErr: true,
+ wantErr: `invalid value for enum type: true`,
}, {
desc: "enum set to JSON null",
inputMessage: &pb3.Enums{},
@@ -494,7 +495,7 @@
inputText: `{
"sString": "camelcase used"
}`,
- wantErr: true,
+ wantErr: `unknown field "sString"`,
}, {
desc: "proto name and json_name",
inputMessage: &pb3.JSONNames{},
@@ -502,7 +503,7 @@
"foo_bar": "json_name used",
"s_string": "proto name used"
}`,
- wantErr: true,
+ wantErr: `(line 3:3): duplicate field "s_string"`,
}, {
desc: "duplicate field names",
inputMessage: &pb3.JSONNames{},
@@ -510,12 +511,12 @@
"foo_bar": "one",
"foo_bar": "two",
}`,
- wantErr: true,
+ wantErr: `(line 3:3): duplicate field "foo_bar"`,
}, {
desc: "null message",
inputMessage: &pb2.Nests{},
inputText: "null",
- wantErr: true,
+ wantErr: `unexpected token null`,
}, {
desc: "proto2 nested message not set",
inputMessage: &pb2.Nests{},
@@ -624,12 +625,12 @@
desc: "message set to non-message",
inputMessage: &pb3.Nests{},
inputText: `"not valid"`,
- wantErr: true,
+ wantErr: `unexpected token "not valid"`,
}, {
desc: "nested message set to non-message",
inputMessage: &pb3.Nests{},
inputText: `{"sNested": true}`,
- wantErr: true,
+ wantErr: `(line 1:13): unexpected token true`,
}, {
desc: "oneof not set",
inputMessage: &pb3.Oneofs{},
@@ -691,7 +692,7 @@
"oneofEnum": "ZERO",
"oneofString": "hello"
}`,
- wantErr: true,
+ wantErr: `(line 3:3): error parsing "oneofString", oneof pb3.Oneofs.union is already set`,
}, {
desc: "oneof set to null and value",
inputMessage: &pb3.Oneofs{},
@@ -793,22 +794,22 @@
desc: "repeated string contains invalid UTF8",
inputMessage: &pb2.Repeats{},
inputText: `{"rptString": ["` + "abc\xff" + `"]}`,
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "repeated messages contain invalid UTF8",
inputMessage: &pb2.Nests{},
inputText: `{"rptNested": [{"optString": "` + "abc\xff" + `"}]}`,
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "repeated scalars contain invalid type",
inputMessage: &pb2.Repeats{},
inputText: `{"rptString": ["hello", null, "world"]}`,
- wantErr: true,
+ wantErr: `invalid value for string type: null`,
}, {
desc: "repeated messages contain invalid type",
inputMessage: &pb2.Nests{},
inputText: `{"rptNested": [{}, null]}`,
- wantErr: true,
+ wantErr: `unexpected token null`,
}, {
desc: "map fields 1",
inputMessage: &pb3.Maps{},
@@ -907,11 +908,11 @@
inputText: `{
"int32ToStr": {
"0": "cero",
- "0": "zero"
+ "0": "zero"
}
}
`,
- wantErr: true,
+ wantErr: `(line 4:5): duplicate map key "0"`,
}, {
desc: "map key empty string",
inputMessage: &pb3.Maps{},
@@ -931,24 +932,27 @@
inputText: `{
"int32ToStr": {
"invalid": "cero"
+ }
}`,
- wantErr: true,
+ wantErr: `invalid value for int32 key: "invalid"`,
}, {
desc: "map contains invalid key 2",
inputMessage: &pb3.Maps{},
inputText: `{
"int32ToStr": {
"1.02": "float"
+ }
}`,
- wantErr: true,
+ wantErr: `invalid value for int32 key: "1.02"`,
}, {
desc: "map contains invalid key 3",
inputMessage: &pb3.Maps{},
inputText: `{
"int32ToStr": {
"2147483648": "exceeds 32-bit integer max limit"
+ }
}`,
- wantErr: true,
+ wantErr: `invalid value for int32 key: "2147483648"`,
}, {
desc: "map contains invalid key 4",
inputMessage: &pb3.Maps{},
@@ -957,7 +961,7 @@
"-1": 0
}
}`,
- wantErr: true,
+ wantErr: `invalid value for uint64 key: "-1"`,
}, {
desc: "map contains invalid value",
inputMessage: &pb3.Maps{},
@@ -965,7 +969,7 @@
"int32ToStr": {
"101": true
}`,
- wantErr: true,
+ wantErr: `invalid value for string type: true`,
}, {
desc: "map contains null for scalar value",
inputMessage: &pb3.Maps{},
@@ -973,7 +977,7 @@
"int32ToStr": {
"101": null
}`,
- wantErr: true,
+ wantErr: `invalid value for string type: null`,
}, {
desc: "map contains null for message value",
inputMessage: &pb3.Maps{},
@@ -982,7 +986,7 @@
"hello": null
}
}`,
- wantErr: true,
+ wantErr: `unexpected token null`,
}, {
desc: "map contains contains message value with invalid UTF8",
inputMessage: &pb3.Maps{},
@@ -993,7 +997,7 @@
}
}
}`,
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "map key contains invalid UTF8",
inputMessage: &pb3.Maps{},
@@ -1002,11 +1006,12 @@
"` + "abc\xff" + `": {}
}
}`,
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "required fields not set",
inputMessage: &pb2.Requireds{},
- wantErr: true,
+ inputText: `{}`,
+ wantErr: errors.RequiredNotSet("pb2.Requireds.req_bool").Error(),
}, {
desc: "required field set",
inputMessage: &pb2.PartialRequired{},
@@ -1031,7 +1036,7 @@
ReqString: proto.String("hello"),
ReqEnum: pb2.Enum_ONE.Enum(),
},
- wantErr: true,
+ wantErr: errors.RequiredNotSet("pb2.Requireds.req_double").Error(),
}, {
desc: "required fields partially set with AllowPartial",
umo: protojson.UnmarshalOptions{AllowPartial: true},
@@ -1076,7 +1081,7 @@
wantMessage: &pb2.IndirectRequired{
OptNested: &pb2.NestedWithRequired{},
},
- wantErr: true,
+ wantErr: errors.RequiredNotSet("pb2.NestedWithRequired.req_string").Error(),
}, {
desc: "indirect required field with AllowPartial",
umo: protojson.UnmarshalOptions{AllowPartial: true},
@@ -1104,7 +1109,7 @@
{},
},
},
- wantErr: true,
+ wantErr: errors.RequiredNotSet("pb2.NestedWithRequired.req_string").Error(),
}, {
desc: "indirect required field in repeated with AllowPartial",
umo: protojson.UnmarshalOptions{AllowPartial: true},
@@ -1142,7 +1147,7 @@
},
},
},
- wantErr: true,
+ wantErr: errors.RequiredNotSet("pb2.NestedWithRequired.req_string").Error(),
}, {
desc: "indirect required field in map with AllowPartial",
umo: protojson.UnmarshalOptions{AllowPartial: true},
@@ -1174,7 +1179,7 @@
OneofNested: &pb2.NestedWithRequired{},
},
},
- wantErr: true,
+ wantErr: errors.RequiredNotSet("pb2.NestedWithRequired.req_string").Error(),
}, {
desc: "indirect required field in oneof with AllowPartial",
umo: protojson.UnmarshalOptions{AllowPartial: true},
@@ -1305,18 +1310,18 @@
desc: "invalid extension field name",
inputMessage: &pb2.Extensions{},
inputText: `{ "[pb2.invalid_message_field]": true }`,
- wantErr: true,
+ wantErr: `(line 1:3): unknown field "[pb2.invalid_message_field]"`,
}, {
desc: "extensions of repeated field contains null",
inputMessage: &pb2.Extensions{},
inputText: `{
"[pb2.ExtensionsContainer.rpt_ext_nested]": [
{"optString": "one"},
- null,
+ null,
{"optString": "three"}
],
}`,
- wantErr: true,
+ wantErr: `(line 4:5): unexpected token null`,
}, {
desc: "MessageSet",
inputMessage: &pb2.MessageSet{},
@@ -1369,7 +1374,7 @@
"optString": "not a messageset extension"
}
}`,
- wantErr: true,
+ wantErr: `unknown field "[pb2.FakeMessageSetExtension]"`,
skip: !flags.ProtoLegacy,
}, {
desc: "not real MessageSet 3",
@@ -1396,7 +1401,7 @@
desc: "Empty contains unknown",
inputMessage: &emptypb.Empty{},
inputText: `{"unknown": null}`,
- wantErr: true,
+ wantErr: `unknown field "unknown"`,
}, {
desc: "BoolValue false",
inputMessage: &wrapperspb.BoolValue{},
@@ -1411,7 +1416,7 @@
desc: "BoolValue invalid value",
inputMessage: &wrapperspb.BoolValue{},
inputText: `{}`,
- wantErr: true,
+ wantErr: `invalid value for bool type: {`,
}, {
desc: "Int32Value",
inputMessage: &wrapperspb.Int32Value{},
@@ -1445,8 +1450,8 @@
}, {
desc: "FloatValue exceeds max limit",
inputMessage: &wrapperspb.FloatValue{},
- inputText: `1.23+40`,
- wantErr: true,
+ inputText: `1.23e+40`,
+ wantErr: `invalid value for float type: 1.23e+40`,
}, {
desc: "FloatValue Infinity",
inputMessage: &wrapperspb.FloatValue{},
@@ -1476,12 +1481,12 @@
desc: "StringValue with invalid UTF8 error",
inputMessage: &wrapperspb.StringValue{},
inputText: "\"abc\xff\"",
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "StringValue field with invalid UTF8 error",
inputMessage: &pb2.KnownTypes{},
inputText: "{\n \"optString\": \"abc\xff\"\n}",
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "NullValue field with JSON null",
inputMessage: &pb2.KnownTypes{},
@@ -1552,7 +1557,7 @@
desc: "Value string with invalid UTF8",
inputMessage: &structpb.Value{},
inputText: "\"\xff\"",
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "Value field string",
inputMessage: &pb2.KnownTypes{},
@@ -1568,7 +1573,7 @@
inputText: `{
"optValue": "` + "\xff" + `"
}`,
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "Value empty struct",
inputMessage: &structpb.Value{},
@@ -1619,7 +1624,7 @@
desc: "Value struct with invalid UTF8 string",
inputMessage: &structpb.Value{},
inputText: "{\"string\": \"abc\xff\"}",
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "Value field struct",
inputMessage: &pb2.KnownTypes{},
@@ -1693,19 +1698,19 @@
desc: "Value list with invalid UTF8 string",
inputMessage: &structpb.Value{},
inputText: "[\"abc\xff\"]",
- wantErr: true,
+ wantErr: `invalid UTF-8`,
}, {
desc: "Value field list with invalid UTF8 string",
inputMessage: &pb2.KnownTypes{},
inputText: `{
"optValue": [ "` + "abc\xff" + `"]
}`,
- wantErr: true,
+ wantErr: `(line 2:17): invalid UTF-8`,
}, {
desc: "Duration empty string",
inputMessage: &durationpb.Duration{},
inputText: `""`,
- wantErr: true,
+ wantErr: `invalid google.protobuf.Duration value ""`,
}, {
desc: "Duration with secs",
inputMessage: &durationpb.Duration{},
@@ -1780,37 +1785,37 @@
desc: "Duration with +secs out of range",
inputMessage: &durationpb.Duration{},
inputText: `"315576000001s"`,
- wantErr: true,
+ wantErr: `google.protobuf.Duration value out of range: "315576000001s"`,
}, {
desc: "Duration with -secs out of range",
inputMessage: &durationpb.Duration{},
inputText: `"-315576000001s"`,
- wantErr: true,
+ wantErr: `google.protobuf.Duration value out of range: "-315576000001s"`,
}, {
desc: "Duration with nanos beyond 9 digits",
inputMessage: &durationpb.Duration{},
inputText: `"0.1000000000s"`,
- wantErr: true,
+ wantErr: `invalid google.protobuf.Duration value "0.1000000000s"`,
}, {
desc: "Duration without suffix s",
inputMessage: &durationpb.Duration{},
inputText: `"123"`,
- wantErr: true,
+ wantErr: `invalid google.protobuf.Duration value "123"`,
}, {
desc: "Duration invalid signed fraction",
inputMessage: &durationpb.Duration{},
inputText: `"123.+123s"`,
- wantErr: true,
+ wantErr: `invalid google.protobuf.Duration value "123.+123s"`,
}, {
desc: "Duration invalid multiple .",
inputMessage: &durationpb.Duration{},
inputText: `"123.123.s"`,
- wantErr: true,
+ wantErr: `invalid google.protobuf.Duration value "123.123.s"`,
}, {
desc: "Duration invalid integer",
inputMessage: &durationpb.Duration{},
inputText: `"01s"`,
- wantErr: true,
+ wantErr: `invalid google.protobuf.Duration value "01s"`,
}, {
desc: "Timestamp zero",
inputMessage: ×tamppb.Timestamp{},
@@ -1845,7 +1850,7 @@
desc: "Timestamp above max value",
inputMessage: ×tamppb.Timestamp{},
inputText: `"9999-12-31T23:59:59-01:00"`,
- wantErr: true,
+ wantErr: `google.protobuf.Timestamp value out of range: "9999-12-31T23:59:59-01:00"`,
}, {
desc: "Timestamp min value",
inputMessage: ×tamppb.Timestamp{},
@@ -1855,12 +1860,12 @@
desc: "Timestamp below min value",
inputMessage: ×tamppb.Timestamp{},
inputText: `"0001-01-01T00:00:00+01:00"`,
- wantErr: true,
+ wantErr: `google.protobuf.Timestamp value out of range: "0001-01-01T00:00:00+01:00"`,
}, {
desc: "Timestamp with nanos beyond 9 digits",
inputMessage: ×tamppb.Timestamp{},
inputText: `"1970-01-01T00:00:00.0000000001Z"`,
- wantErr: true,
+ wantErr: `invalid google.protobuf.Timestamp value`,
}, {
desc: "FieldMask empty",
inputMessage: &fieldmaskpb.FieldMask{},
@@ -1933,7 +1938,7 @@
umo: protojson.UnmarshalOptions{Resolver: new(preg.Types)},
inputMessage: &anypb.Any{},
inputText: `{"@type": "foo/pb2.Nested"}`,
- wantErr: true,
+ wantErr: `(line 1:11): unable to resolve "foo/pb2.Nested":`,
}, {
desc: "Any with missing required",
inputMessage: &anypb.Any{},
@@ -1990,7 +1995,7 @@
"optString": "` + "abc\xff" + `",
"@type": "foo/pb2.Nested"
}`,
- wantErr: true,
+ wantErr: `(line 2:16): invalid UTF-8`,
}, {
desc: "Any with BoolValue",
inputMessage: &anypb.Any{},
@@ -2025,7 +2030,7 @@
inputText: `{
"@type": "type.googleapis.com/google.protobuf.Empty"
}`,
- wantErr: true,
+ wantErr: `(line 3:1): missing "value" field`,
}, {
desc: "Any with StringValue containing invalid UTF8",
inputMessage: &anypb.Any{},
@@ -2033,7 +2038,7 @@
"@type": "google.protobuf.StringValue",
"value": "` + "abc\xff" + `"
}`,
- wantErr: true,
+ wantErr: `(line 3:12): invalid UTF-8`,
}, {
desc: "Any with Int64Value",
inputMessage: &anypb.Any{},
@@ -2059,7 +2064,7 @@
"@type": "google.protobuf.Int64Value",
"value": "forty-two"
}`,
- wantErr: true,
+ wantErr: `(line 3:12): invalid value for int64 type: "forty-two"`,
}, {
desc: "Any with invalid UInt64Value",
inputMessage: &anypb.Any{},
@@ -2067,7 +2072,7 @@
"@type": "google.protobuf.UInt64Value",
"value": -42
}`,
- wantErr: true,
+ wantErr: `(line 3:12): invalid value for uint64 type: -42`,
}, {
desc: "Any with Duration",
inputMessage: &anypb.Any{},
@@ -2093,7 +2098,7 @@
"@type": "google.protobuf.Value",
"value": "` + "abc\xff" + `"
}`,
- wantErr: true,
+ wantErr: `(line 3:12): invalid UTF-8`,
}, {
desc: "Any with Value of NullValue",
inputMessage: &anypb.Any{},
@@ -2159,14 +2164,14 @@
inputText: `{
"value": {}
}`,
- wantErr: true,
+ wantErr: `(line 1:1): missing "@type" field`,
}, {
desc: "Any with empty @type",
inputMessage: &anypb.Any{},
inputText: `{
"@type": ""
}`,
- wantErr: true,
+ wantErr: `(line 2:12): @type field contains empty value`,
}, {
desc: "Any with duplicate @type",
inputMessage: &anypb.Any{},
@@ -2175,7 +2180,7 @@
"value": "hello",
"@type": "pb2.Nested"
}`,
- wantErr: true,
+ wantErr: `(line 4:3): duplicate "@type" field`,
}, {
desc: "Any with duplicate value",
inputMessage: &anypb.Any{},
@@ -2184,7 +2189,7 @@
"value": "hello",
"value": "world"
}`,
- wantErr: true,
+ wantErr: `(line 4:3): duplicate "value" field`,
}, {
desc: "Any with unknown field",
inputMessage: &anypb.Any{},
@@ -2193,7 +2198,7 @@
"optString": "hello",
"unknown": "world"
}`,
- wantErr: true,
+ wantErr: `(line 4:3): unknown field "unknown"`,
}, {
desc: "Any with embedded type containing Any",
inputMessage: &anypb.Any{},
@@ -2201,10 +2206,10 @@
"@type": "pb2.KnownTypes",
"optAny": {
"@type": "google.protobuf.StringValue",
- "value": "` + "abc\xff" + `"
+ "value": "` + "abc\xff" + `"
}
}`,
- wantErr: true,
+ wantErr: `(line 5:14): invalid UTF-8`,
}, {
desc: "well known types as field values",
inputMessage: &pb2.KnownTypes{},
@@ -2396,7 +2401,7 @@
desc: "weak fields; unknown field",
inputMessage: &testpb.TestWeak{},
inputText: `{"weak_message1":{"a":1}, "weak_message2":{"a":1}}`,
- wantErr: true, // weak_message2 is unknown since the package containing it is not imported
+ wantErr: `unknown field "weak_message2"`, // weak_message2 is unknown since the package containing it is not imported
skip: !flags.ProtoLegacy,
}}
@@ -2407,11 +2412,16 @@
}
t.Run(tt.desc, func(t *testing.T) {
err := tt.umo.Unmarshal([]byte(tt.inputText), tt.inputMessage)
- if err != nil && !tt.wantErr {
- t.Errorf("Unmarshal() returned error: %v\n\n", err)
+ if err != nil {
+ if tt.wantErr == "" {
+ t.Errorf("Unmarshal() got unexpected error: %v", err)
+ } else if !strings.Contains(err.Error(), tt.wantErr) {
+ t.Errorf("Unmarshal() error got %q, want %q", err, tt.wantErr)
+ }
+ return
}
- if err == nil && tt.wantErr {
- t.Error("Unmarshal() got nil error, want error\n\n")
+ if tt.wantErr != "" {
+ t.Errorf("Unmarshal() got nil error, want error %q", tt.wantErr)
}
if tt.wantMessage != nil && !proto.Equal(tt.inputMessage, tt.wantMessage) {
t.Errorf("Unmarshal()\n<got>\n%v\n<want>\n%v\n", tt.inputMessage, tt.wantMessage)
diff --git a/encoding/protojson/encode.go b/encoding/protojson/encode.go
index 02723c0..ad29455 100644
--- a/encoding/protojson/encode.go
+++ b/encoding/protojson/encode.go
@@ -39,7 +39,6 @@
// MarshalOptions is a configurable JSON format marshaler.
type MarshalOptions struct {
pragma.NoUnkeyedLiterals
- encoder *json.Encoder
// AllowPartial allows messages that have missing required fields to marshal
// without returning an error. If AllowPartial is false (the default),
@@ -112,31 +111,35 @@
o.Resolver = protoregistry.GlobalTypes
}
- var err error
- o.encoder, err = json.NewEncoder(o.Indent)
+ internalEnc, err := json.NewEncoder(o.Indent)
if err != nil {
return nil, err
}
- err = o.marshalMessage(m.ProtoReflect())
- if err != nil {
+ enc := encoder{internalEnc, o}
+ if err := enc.marshalMessage(m.ProtoReflect()); err != nil {
return nil, err
}
if o.AllowPartial {
- return o.encoder.Bytes(), nil
+ return enc.Bytes(), nil
}
- return o.encoder.Bytes(), proto.IsInitialized(m)
+ return enc.Bytes(), proto.IsInitialized(m)
+}
+
+type encoder struct {
+ *json.Encoder
+ opts MarshalOptions
}
// marshalMessage marshals the given protoreflect.Message.
-func (o MarshalOptions) marshalMessage(m pref.Message) error {
+func (e encoder) marshalMessage(m pref.Message) error {
if isCustomType(m.Descriptor().FullName()) {
- return o.marshalCustomType(m)
+ return e.marshalCustomType(m)
}
- o.encoder.StartObject()
- defer o.encoder.EndObject()
- if err := o.marshalFields(m); err != nil {
+ e.StartObject()
+ defer e.EndObject()
+ if err := e.marshalFields(m); err != nil {
return err
}
@@ -144,7 +147,7 @@
}
// marshalFields marshals the fields in the given protoreflect.Message.
-func (o MarshalOptions) marshalFields(m pref.Message) error {
+func (e encoder) marshalFields(m pref.Message) error {
messageDesc := m.Descriptor()
if !flags.ProtoLegacy && messageset.IsMessageSet(messageDesc) {
return errors.New("no support for proto1 MessageSets")
@@ -166,7 +169,7 @@
val := m.Get(fd)
if !m.Has(fd) {
- if !o.EmitUnpopulated {
+ if !e.opts.EmitUnpopulated {
continue
}
isProto2Scalar := fd.Syntax() == pref.Proto2 && fd.Default().IsValid()
@@ -178,99 +181,93 @@
}
name := fd.JSONName()
- if o.UseProtoNames {
+ if e.opts.UseProtoNames {
name = string(fd.Name())
// Use type name for group field name.
if fd.Kind() == pref.GroupKind {
name = string(fd.Message().Name())
}
}
- if err := o.encoder.WriteName(name); err != nil {
+ if err := e.WriteName(name); err != nil {
return err
}
- if err := o.marshalValue(val, fd); err != nil {
+ if err := e.marshalValue(val, fd); err != nil {
return err
}
}
// Marshal out extensions.
- if err := o.marshalExtensions(m); err != nil {
+ if err := e.marshalExtensions(m); err != nil {
return err
}
return nil
}
// marshalValue marshals the given protoreflect.Value.
-func (o MarshalOptions) marshalValue(val pref.Value, fd pref.FieldDescriptor) error {
+func (e encoder) marshalValue(val pref.Value, fd pref.FieldDescriptor) error {
switch {
case fd.IsList():
- return o.marshalList(val.List(), fd)
+ return e.marshalList(val.List(), fd)
case fd.IsMap():
- return o.marshalMap(val.Map(), fd)
+ return e.marshalMap(val.Map(), fd)
default:
- return o.marshalSingular(val, fd)
+ return e.marshalSingular(val, fd)
}
}
// marshalSingular marshals the given non-repeated field value. This includes
// all scalar types, enums, messages, and groups.
-func (o MarshalOptions) marshalSingular(val pref.Value, fd pref.FieldDescriptor) error {
+func (e encoder) marshalSingular(val pref.Value, fd pref.FieldDescriptor) error {
if !val.IsValid() {
- o.encoder.WriteNull()
+ e.WriteNull()
return nil
}
switch kind := fd.Kind(); kind {
case pref.BoolKind:
- o.encoder.WriteBool(val.Bool())
+ e.WriteBool(val.Bool())
case pref.StringKind:
- if err := o.encoder.WriteString(val.String()); err != nil {
- return err
+ if e.WriteString(val.String()) != nil {
+ return errors.InvalidUTF8(string(fd.FullName()))
}
case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind:
- o.encoder.WriteInt(val.Int())
+ e.WriteInt(val.Int())
case pref.Uint32Kind, pref.Fixed32Kind:
- o.encoder.WriteUint(val.Uint())
+ e.WriteUint(val.Uint())
case pref.Int64Kind, pref.Sint64Kind, pref.Uint64Kind,
pref.Sfixed64Kind, pref.Fixed64Kind:
// 64-bit integers are written out as JSON string.
- o.encoder.WriteString(val.String())
+ e.WriteString(val.String())
case pref.FloatKind:
// Encoder.WriteFloat handles the special numbers NaN and infinites.
- o.encoder.WriteFloat(val.Float(), 32)
+ e.WriteFloat(val.Float(), 32)
case pref.DoubleKind:
// Encoder.WriteFloat handles the special numbers NaN and infinites.
- o.encoder.WriteFloat(val.Float(), 64)
+ e.WriteFloat(val.Float(), 64)
case pref.BytesKind:
- err := o.encoder.WriteString(base64.StdEncoding.EncodeToString(val.Bytes()))
- if err != nil {
- return err
- }
+ e.WriteString(base64.StdEncoding.EncodeToString(val.Bytes()))
case pref.EnumKind:
if fd.Enum().FullName() == "google.protobuf.NullValue" {
- o.encoder.WriteNull()
+ e.WriteNull()
} else {
desc := fd.Enum().Values().ByNumber(val.Enum())
- if o.UseEnumNumbers || desc == nil {
- o.encoder.WriteInt(int64(val.Enum()))
+ if e.opts.UseEnumNumbers || desc == nil {
+ e.WriteInt(int64(val.Enum()))
} else {
- err := o.encoder.WriteString(string(desc.Name()))
- if err != nil {
- return err
- }
+ e.WriteString(string(desc.Name()))
}
}
case pref.MessageKind, pref.GroupKind:
- if err := o.marshalMessage(val.Message()); err != nil {
+ if err := e.marshalMessage(val.Message()); err != nil {
return err
}
@@ -281,13 +278,13 @@
}
// marshalList marshals the given protoreflect.List.
-func (o MarshalOptions) marshalList(list pref.List, fd pref.FieldDescriptor) error {
- o.encoder.StartArray()
- defer o.encoder.EndArray()
+func (e encoder) marshalList(list pref.List, fd pref.FieldDescriptor) error {
+ e.StartArray()
+ defer e.EndArray()
for i := 0; i < list.Len(); i++ {
item := list.Get(i)
- if err := o.marshalSingular(item, fd); err != nil {
+ if err := e.marshalSingular(item, fd); err != nil {
return err
}
}
@@ -300,9 +297,9 @@
}
// marshalMap marshals given protoreflect.Map.
-func (o MarshalOptions) marshalMap(mmap pref.Map, fd pref.FieldDescriptor) error {
- o.encoder.StartObject()
- defer o.encoder.EndObject()
+func (e encoder) marshalMap(mmap pref.Map, fd pref.FieldDescriptor) error {
+ e.StartObject()
+ defer e.EndObject()
// Get a sorted list based on keyType first.
entries := make([]mapEntry, 0, mmap.Len())
@@ -314,10 +311,10 @@
// Write out sorted list.
for _, entry := range entries {
- if err := o.encoder.WriteName(entry.key.String()); err != nil {
+ if err := e.WriteName(entry.key.String()); err != nil {
return err
}
- if err := o.marshalSingular(entry.value, fd.MapValue()); err != nil {
+ if err := e.marshalSingular(entry.value, fd.MapValue()); err != nil {
return err
}
}
@@ -341,7 +338,7 @@
}
// marshalExtensions marshals extension fields.
-func (o MarshalOptions) marshalExtensions(m pref.Message) error {
+func (e encoder) marshalExtensions(m pref.Message) error {
type entry struct {
key string
value pref.Value
@@ -380,10 +377,10 @@
// JSON field name is the proto field name enclosed in [], similar to
// textproto. This is consistent with Go v1 lib. C++ lib v3.7.0 does not
// marshal out extension fields.
- if err := o.encoder.WriteName("[" + entry.key + "]"); err != nil {
+ if err := e.WriteName("[" + entry.key + "]"); err != nil {
return err
}
- if err := o.marshalValue(entry.value, entry.desc); err != nil {
+ if err := e.marshalValue(entry.value, entry.desc); err != nil {
return err
}
}
diff --git a/encoding/protojson/well_known_types.go b/encoding/protojson/well_known_types.go
index a30b590..e5e9b2e 100644
--- a/encoding/protojson/well_known_types.go
+++ b/encoding/protojson/well_known_types.go
@@ -49,11 +49,11 @@
// marshalCustomType marshals 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 (o MarshalOptions) marshalCustomType(m pref.Message) error {
+func (e encoder) marshalCustomType(m pref.Message) error {
name := m.Descriptor().FullName()
switch name {
case "google.protobuf.Any":
- return o.marshalAny(m)
+ return e.marshalAny(m)
case "google.protobuf.BoolValue",
"google.protobuf.DoubleValue",
@@ -64,41 +64,41 @@
"google.protobuf.UInt64Value",
"google.protobuf.StringValue",
"google.protobuf.BytesValue":
- return o.marshalWrapperType(m)
+ return e.marshalWrapperType(m)
case "google.protobuf.Empty":
- return o.marshalEmpty(m)
+ return e.marshalEmpty(m)
case "google.protobuf.Struct":
- return o.marshalStruct(m)
+ return e.marshalStruct(m)
case "google.protobuf.ListValue":
- return o.marshalListValue(m)
+ return e.marshalListValue(m)
case "google.protobuf.Value":
- return o.marshalKnownValue(m)
+ return e.marshalKnownValue(m)
case "google.protobuf.Duration":
- return o.marshalDuration(m)
+ return e.marshalDuration(m)
case "google.protobuf.Timestamp":
- return o.marshalTimestamp(m)
+ return e.marshalTimestamp(m)
case "google.protobuf.FieldMask":
- return o.marshalFieldMask(m)
+ return e.marshalFieldMask(m)
}
- panic(fmt.Sprintf("%q does not have a custom marshaler", name))
+ panic(fmt.Sprintf("%s does not have a custom marshaler", name))
}
// 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 (o UnmarshalOptions) unmarshalCustomType(m pref.Message) error {
+func (d decoder) unmarshalCustomType(m pref.Message) error {
name := m.Descriptor().FullName()
switch name {
case "google.protobuf.Any":
- return o.unmarshalAny(m)
+ return d.unmarshalAny(m)
case "google.protobuf.BoolValue",
"google.protobuf.DoubleValue",
@@ -109,31 +109,31 @@
"google.protobuf.UInt64Value",
"google.protobuf.StringValue",
"google.protobuf.BytesValue":
- return o.unmarshalWrapperType(m)
+ return d.unmarshalWrapperType(m)
case "google.protobuf.Empty":
- return o.unmarshalEmpty(m)
+ return d.unmarshalEmpty(m)
case "google.protobuf.Struct":
- return o.unmarshalStruct(m)
+ return d.unmarshalStruct(m)
case "google.protobuf.ListValue":
- return o.unmarshalListValue(m)
+ return d.unmarshalListValue(m)
case "google.protobuf.Value":
- return o.unmarshalKnownValue(m)
+ return d.unmarshalKnownValue(m)
case "google.protobuf.Duration":
- return o.unmarshalDuration(m)
+ return d.unmarshalDuration(m)
case "google.protobuf.Timestamp":
- return o.unmarshalTimestamp(m)
+ return d.unmarshalTimestamp(m)
case "google.protobuf.FieldMask":
- return o.unmarshalFieldMask(m)
+ return d.unmarshalFieldMask(m)
}
- panic(fmt.Sprintf("%q does not have a custom unmarshaler", name))
+ panic(fmt.Sprintf("%s does not have a custom unmarshaler", name))
}
// The JSON representation of an Any message uses the regular representation of
@@ -142,14 +142,14 @@
// custom JSON representation, that representation will be embedded adding a
// field `value` which holds the custom JSON in addition to the `@type` field.
-func (o MarshalOptions) marshalAny(m pref.Message) error {
+func (e encoder) marshalAny(m pref.Message) error {
fds := m.Descriptor().Fields()
fdType := fds.ByNumber(fieldnum.Any_TypeUrl)
fdValue := fds.ByNumber(fieldnum.Any_Value)
// Start writing the JSON object.
- o.encoder.StartObject()
- defer o.encoder.EndObject()
+ e.StartObject()
+ defer e.EndObject()
if !m.Has(fdType) {
if !m.Has(fdValue) {
@@ -166,13 +166,13 @@
// Marshal out @type field.
typeURL := typeVal.String()
- o.encoder.WriteName("@type")
- if err := o.encoder.WriteString(typeURL); err != nil {
+ e.WriteName("@type")
+ if err := e.WriteString(typeURL); err != nil {
return err
}
// Resolve the type in order to unmarshal value field.
- emt, err := o.Resolver.FindMessageByURL(typeURL)
+ emt, err := e.opts.Resolver.FindMessageByURL(typeURL)
if err != nil {
return errors.New("%s: unable to resolve %q: %v", m.Descriptor().FullName(), typeURL, err)
}
@@ -180,7 +180,7 @@
em := emt.New()
err = proto.UnmarshalOptions{
AllowPartial: true, // never check required fields inside an Any
- Resolver: o.Resolver,
+ Resolver: e.opts.Resolver,
}.Unmarshal(valueVal.Bytes(), em.Interface())
if err != nil {
return errors.New("%s: unable to unmarshal %q: %v", m.Descriptor().FullName(), typeURL, err)
@@ -190,71 +190,82 @@
// with corresponding custom JSON encoding of the embedded message as a
// field.
if isCustomType(emt.Descriptor().FullName()) {
- o.encoder.WriteName("value")
- return o.marshalCustomType(em)
+ e.WriteName("value")
+ return e.marshalCustomType(em)
}
// Else, marshal out the embedded message's fields in this Any object.
- if err := o.marshalFields(em); err != nil {
+ if err := e.marshalFields(em); err != nil {
return err
}
return nil
}
-func (o UnmarshalOptions) unmarshalAny(m pref.Message) error {
- // Use Peek to check for json.StartObject to avoid advancing a read.
- if o.decoder.Peek() != json.StartObject {
- jval, _ := o.decoder.Read()
- return unexpectedJSONError{jval}
+func (d decoder) unmarshalAny(m pref.Message) error {
+ // Peek to check for json.ObjectOpen to avoid advancing a read.
+ start, err := d.Peek()
+ if err != nil {
+ return err
+ }
+ if start.Kind() != json.ObjectOpen {
+ return d.unexpectedTokenError(start)
}
- // Use another json.Decoder to parse the unread bytes from o.decoder for
- // @type field. This avoids advancing a read from o.decoder because the
- // current JSON object may contain the fields of the embedded type.
- dec := o.decoder.Clone()
- typeURL, err := findTypeURL(dec)
- if err == errEmptyObject {
+ // Use another decoder to parse the unread bytes for @type field. This
+ // avoids advancing a read from current decoder because the current JSON
+ // object may contain the fields of the embedded type.
+ dec := decoder{d.Clone(), UnmarshalOptions{}}
+ tok, err := findTypeURL(dec)
+ switch err {
+ case errEmptyObject:
// An empty JSON object translates to an empty Any message.
- o.decoder.Read() // Read json.StartObject.
- o.decoder.Read() // Read json.EndObject.
+ d.Read() // Read json.ObjectOpen.
+ d.Read() // Read json.ObjectClose.
return nil
- }
- if o.DiscardUnknown && err == errMissingType {
- // Treat all fields as unknowns, similar to an empty object.
- return skipJSONValue(o.decoder)
- }
- if err != nil {
- return errors.New("google.protobuf.Any: %v", err)
+
+ case errMissingType:
+ if d.opts.DiscardUnknown {
+ // Treat all fields as unknowns, similar to an empty object.
+ return d.skipJSONValue()
+ }
+ // Use start.Pos() for line position.
+ return d.newError(start.Pos(), err.Error())
+
+ default:
+ if err != nil {
+ return err
+ }
}
- emt, err := o.Resolver.FindMessageByURL(typeURL)
+ typeURL := tok.ParsedString()
+ emt, err := d.opts.Resolver.FindMessageByURL(typeURL)
if err != nil {
- return errors.New("google.protobuf.Any: unable to resolve type %q: %v", typeURL, err)
+ return d.newError(tok.Pos(), "unable to resolve %v: %q", tok.RawString(), err)
}
// Create new message for the embedded message type and unmarshal into it.
em := emt.New()
if isCustomType(emt.Descriptor().FullName()) {
- // If embedded message is a custom type, unmarshal the JSON "value" field
- // into it.
- if err := o.unmarshalAnyValue(em); err != nil {
- return errors.New("google.protobuf.Any: %v", err)
+ // If embedded message is a custom type,
+ // unmarshal the JSON "value" field into it.
+ if err := d.unmarshalAnyValue(em); err != nil {
+ return err
}
} else {
// Else unmarshal the current JSON object into it.
- if err := o.unmarshalMessage(em, true); err != nil {
- return errors.New("google.protobuf.Any: %v", err)
+ if err := d.unmarshalMessage(em, true); err != nil {
+ return err
}
}
// Serialize the embedded message and assign the resulting bytes to the
// proto value field.
b, err := proto.MarshalOptions{
- AllowPartial: true, // never check required fields inside an Any
+ AllowPartial: true, // No need to check required fields inside an Any.
Deterministic: true,
}.Marshal(em.Interface())
if err != nil {
- return errors.New("google.protobuf.Any: %v", err)
+ return d.newError(start.Pos(), "error in marshaling Any.value field: %v", err)
}
fds := m.Descriptor().Fields()
@@ -266,113 +277,112 @@
return nil
}
-var errEmptyObject = errors.New(`empty object`)
-var errMissingType = errors.New(`missing "@type" field`)
+var errEmptyObject = fmt.Errorf(`empty object`)
+var errMissingType = fmt.Errorf(`missing "@type" field`)
-// findTypeURL returns the "@type" field value from the given JSON bytes. It is
-// expected that the given bytes start with json.StartObject. It returns
-// errEmptyObject if the JSON object is empty. It returns error if the object
-// does not contain the field or other decoding problems.
-func findTypeURL(dec *json.Decoder) (string, error) {
+// findTypeURL returns the token for the "@type" field value from the given
+// JSON bytes. It is expected that the given bytes start with json.ObjectOpen.
+// It returns errEmptyObject if the JSON object is empty or errMissingType if
+// @type field does not exist. It returns other error if the @type field is not
+// valid or other decoding issues.
+func findTypeURL(d decoder) (json.Token, error) {
var typeURL string
+ var typeTok json.Token
numFields := 0
// Skip start object.
- dec.Read()
+ d.Read()
Loop:
for {
- jval, err := dec.Read()
+ tok, err := d.Read()
if err != nil {
- return "", err
+ return json.Token{}, err
}
- switch jval.Type() {
- case json.EndObject:
+ switch tok.Kind() {
+ case json.ObjectClose:
if typeURL == "" {
// Did not find @type field.
if numFields > 0 {
- return "", errMissingType
+ return json.Token{}, errMissingType
}
- return "", errEmptyObject
+ return json.Token{}, errEmptyObject
}
break Loop
case json.Name:
numFields++
- name, err := jval.Name()
- if err != nil {
- return "", err
- }
- if name != "@type" {
+ if tok.Name() != "@type" {
// Skip value.
- if err := skipJSONValue(dec); err != nil {
- return "", err
+ if err := d.skipJSONValue(); err != nil {
+ return json.Token{}, err
}
continue
}
// Return error if this was previously set already.
if typeURL != "" {
- return "", errors.New(`duplicate "@type" field`)
+ return json.Token{}, d.newError(tok.Pos(), `duplicate "@type" field`)
}
// Read field value.
- jval, err := dec.Read()
+ tok, err := d.Read()
if err != nil {
- return "", err
+ return json.Token{}, err
}
- if jval.Type() != json.String {
- return "", unexpectedJSONError{jval}
+ if tok.Kind() != json.String {
+ return json.Token{}, d.newError(tok.Pos(), `@type field value is not a string: %v`, tok.RawString())
}
- typeURL = jval.String()
+ typeURL = tok.ParsedString()
if typeURL == "" {
- return "", errors.New(`"@type" field contains empty value`)
+ return json.Token{}, d.newError(tok.Pos(), `@type field contains empty value`)
}
+ typeTok = tok
}
}
- return typeURL, nil
+ return typeTok, nil
}
-// skipJSONValue makes the given decoder parse a JSON value (null, boolean,
-// string, number, object and array) in order to advance the read to the next
-// JSON value. It relies on Decoder.Read returning an error if the types are
-// not in valid sequence.
-func skipJSONValue(dec *json.Decoder) error {
- jval, err := dec.Read()
+// skipJSONValue parses a JSON value (null, boolean, string, number, object and
+// array) in order to advance the read to the next JSON value. It relies on
+// the decoder returning an error if the types are not in valid sequence.
+func (d decoder) skipJSONValue() error {
+ tok, err := d.Read()
if err != nil {
return err
}
// Only need to continue reading for objects and arrays.
- switch jval.Type() {
- case json.StartObject:
+ switch tok.Kind() {
+ case json.ObjectOpen:
for {
- jval, err := dec.Read()
+ tok, err := d.Read()
if err != nil {
return err
}
- switch jval.Type() {
- case json.EndObject:
+ switch tok.Kind() {
+ case json.ObjectClose:
return nil
case json.Name:
// Skip object field value.
- if err := skipJSONValue(dec); err != nil {
+ if err := d.skipJSONValue(); err != nil {
return err
}
}
}
- case json.StartArray:
+ case json.ArrayOpen:
for {
- switch dec.Peek() {
- case json.EndArray:
- dec.Read()
- return nil
- case json.Invalid:
- _, err := dec.Read()
+ tok, err := d.Peek()
+ if err != nil {
return err
+ }
+ switch tok.Kind() {
+ case json.ArrayClose:
+ d.Read()
+ return nil
default:
// Skip array item.
- if err := skipJSONValue(dec); err != nil {
+ if err := d.skipJSONValue(); err != nil {
return err
}
}
@@ -383,51 +393,47 @@
// unmarshalAnyValue unmarshals the given custom-type message from the JSON
// object's "value" field.
-func (o UnmarshalOptions) unmarshalAnyValue(m pref.Message) error {
- // Skip StartObject, and start reading the fields.
- o.decoder.Read()
+func (d decoder) unmarshalAnyValue(m pref.Message) error {
+ // Skip ObjectOpen, and start reading the fields.
+ d.Read()
var found bool // Used for detecting duplicate "value".
for {
- jval, err := o.decoder.Read()
+ tok, err := d.Read()
if err != nil {
return err
}
- switch jval.Type() {
- case json.EndObject:
+ switch tok.Kind() {
+ case json.ObjectClose:
if !found {
- return errors.New(`missing "value" field`)
+ return d.newError(tok.Pos(), `missing "value" field`)
}
return nil
case json.Name:
- name, err := jval.Name()
- if err != nil {
- return err
- }
- switch name {
+ switch tok.Name() {
+ case "@type":
+ // Skip the value as this was previously parsed already.
+ d.Read()
+
+ case "value":
+ if found {
+ return d.newError(tok.Pos(), `duplicate "value" field`)
+ }
+ // Unmarshal the field value into the given message.
+ if err := d.unmarshalCustomType(m); err != nil {
+ return err
+ }
+ found = true
+
default:
- if o.DiscardUnknown {
- if err := skipJSONValue(o.decoder); err != nil {
+ if d.opts.DiscardUnknown {
+ if err := d.skipJSONValue(); err != nil {
return err
}
continue
}
- return errors.New("unknown field %q", name)
-
- case "@type":
- // Skip the value as this was previously parsed already.
- o.decoder.Read()
-
- case "value":
- if found {
- return errors.New(`duplicate "value" field`)
- }
- // Unmarshal the field value into the given message.
- if err := o.unmarshalCustomType(m); err != nil {
- return err
- }
- found = true
+ return d.newError(tok.Pos(), "unknown field %v", tok.RawString())
}
}
}
@@ -438,15 +444,15 @@
// The "value" field has the same field number for all wrapper types.
const wrapperFieldNumber = fieldnum.BoolValue_Value
-func (o MarshalOptions) marshalWrapperType(m pref.Message) error {
+func (e encoder) marshalWrapperType(m pref.Message) error {
fd := m.Descriptor().Fields().ByNumber(wrapperFieldNumber)
val := m.Get(fd)
- return o.marshalSingular(val, fd)
+ return e.marshalSingular(val, fd)
}
-func (o UnmarshalOptions) unmarshalWrapperType(m pref.Message) error {
+func (d decoder) unmarshalWrapperType(m pref.Message) error {
fd := m.Descriptor().Fields().ByNumber(wrapperFieldNumber)
- val, err := o.unmarshalScalar(fd)
+ val, err := d.unmarshalScalar(fd)
if err != nil {
return err
}
@@ -456,42 +462,41 @@
// The JSON representation for Empty is an empty JSON object.
-func (o MarshalOptions) marshalEmpty(pref.Message) error {
- o.encoder.StartObject()
- o.encoder.EndObject()
+func (e encoder) marshalEmpty(pref.Message) error {
+ e.StartObject()
+ e.EndObject()
return nil
}
-func (o UnmarshalOptions) unmarshalEmpty(pref.Message) error {
- jval, err := o.decoder.Read()
+func (d decoder) unmarshalEmpty(pref.Message) error {
+ tok, err := d.Read()
if err != nil {
return err
}
- if jval.Type() != json.StartObject {
- return unexpectedJSONError{jval}
+ if tok.Kind() != json.ObjectOpen {
+ return d.unexpectedTokenError(tok)
}
for {
- jval, err := o.decoder.Read()
+ tok, err := d.Read()
if err != nil {
return err
}
- switch jval.Type() {
- case json.EndObject:
+ switch tok.Kind() {
+ case json.ObjectClose:
return nil
case json.Name:
- if o.DiscardUnknown {
- if err := skipJSONValue(o.decoder); err != nil {
+ if d.opts.DiscardUnknown {
+ if err := d.skipJSONValue(); err != nil {
return err
}
continue
}
- name, _ := jval.Name()
- return errors.New("unknown field %q", name)
+ return d.newError(tok.Pos(), "unknown field %v", tok.RawString())
default:
- return unexpectedJSONError{jval}
+ return d.unexpectedTokenError(tok)
}
}
}
@@ -499,73 +504,76 @@
// The JSON representation for Struct is a JSON object that contains the encoded
// Struct.fields map and follows the serialization rules for a map.
-func (o MarshalOptions) marshalStruct(m pref.Message) error {
+func (e encoder) marshalStruct(m pref.Message) error {
fd := m.Descriptor().Fields().ByNumber(fieldnum.Struct_Fields)
- return o.marshalMap(m.Get(fd).Map(), fd)
+ return e.marshalMap(m.Get(fd).Map(), fd)
}
-func (o UnmarshalOptions) unmarshalStruct(m pref.Message) error {
+func (d decoder) unmarshalStruct(m pref.Message) error {
fd := m.Descriptor().Fields().ByNumber(fieldnum.Struct_Fields)
- return o.unmarshalMap(m.Mutable(fd).Map(), fd)
+ return d.unmarshalMap(m.Mutable(fd).Map(), fd)
}
// The JSON representation for ListValue is JSON array that contains the encoded
// ListValue.values repeated field and follows the serialization rules for a
// repeated field.
-func (o MarshalOptions) marshalListValue(m pref.Message) error {
+func (e encoder) marshalListValue(m pref.Message) error {
fd := m.Descriptor().Fields().ByNumber(fieldnum.ListValue_Values)
- return o.marshalList(m.Get(fd).List(), fd)
+ return e.marshalList(m.Get(fd).List(), fd)
}
-func (o UnmarshalOptions) unmarshalListValue(m pref.Message) error {
+func (d decoder) unmarshalListValue(m pref.Message) error {
fd := m.Descriptor().Fields().ByNumber(fieldnum.ListValue_Values)
- return o.unmarshalList(m.Mutable(fd).List(), fd)
+ return d.unmarshalList(m.Mutable(fd).List(), fd)
}
// The JSON representation for a Value is dependent on the oneof field that is
// set. Each of the field in the oneof has its own custom serialization rule. A
// Value message needs to be a oneof field set, else it is an error.
-func (o MarshalOptions) marshalKnownValue(m pref.Message) error {
+func (e encoder) marshalKnownValue(m pref.Message) error {
od := m.Descriptor().Oneofs().ByName("kind")
fd := m.WhichOneof(od)
if fd == nil {
return errors.New("%s: none of the oneof fields is set", m.Descriptor().FullName())
}
- return o.marshalSingular(m.Get(fd), fd)
+ return e.marshalSingular(m.Get(fd), fd)
}
-func (o UnmarshalOptions) unmarshalKnownValue(m pref.Message) error {
- switch o.decoder.Peek() {
+func (d decoder) unmarshalKnownValue(m pref.Message) error {
+ tok, err := d.Peek()
+ if err != nil {
+ return err
+ }
+
+ var fd pref.FieldDescriptor
+ var val pref.Value
+ switch tok.Kind() {
case json.Null:
- o.decoder.Read()
- fd := m.Descriptor().Fields().ByNumber(fieldnum.Value_NullValue)
- m.Set(fd, pref.ValueOfEnum(0))
+ d.Read()
+ fd = m.Descriptor().Fields().ByNumber(fieldnum.Value_NullValue)
+ val = pref.ValueOfEnum(0)
case json.Bool:
- jval, err := o.decoder.Read()
+ tok, err := d.Read()
if err != nil {
return err
}
- val, err := unmarshalBool(jval)
- if err != nil {
- return err
- }
- fd := m.Descriptor().Fields().ByNumber(fieldnum.Value_BoolValue)
- m.Set(fd, val)
+ fd = m.Descriptor().Fields().ByNumber(fieldnum.Value_BoolValue)
+ val = pref.ValueOfBool(tok.Bool())
case json.Number:
- jval, err := o.decoder.Read()
+ tok, err := d.Read()
if err != nil {
return err
}
- val, err := unmarshalFloat(jval, 64)
- if err != nil {
- return err
+ fd = m.Descriptor().Fields().ByNumber(fieldnum.Value_NumberValue)
+ var ok bool
+ val, ok = unmarshalFloat(tok, 64)
+ if !ok {
+ return d.newError(tok.Pos(), "invalid google.protobuf.Value: %v", tok.RawString())
}
- fd := m.Descriptor().Fields().ByNumber(fieldnum.Value_NumberValue)
- m.Set(fd, val)
case json.String:
// A JSON string may have been encoded from the number_value field,
@@ -574,40 +582,32 @@
// 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 := o.decoder.Read()
+ tok, err := d.Read()
if err != nil {
return err
}
- val, err := unmarshalString(jval)
- if err != nil {
- return err
- }
- fd := m.Descriptor().Fields().ByNumber(fieldnum.Value_StringValue)
- m.Set(fd, val)
+ fd = m.Descriptor().Fields().ByNumber(fieldnum.Value_StringValue)
+ val = pref.ValueOfString(tok.ParsedString())
- case json.StartObject:
- fd := m.Descriptor().Fields().ByNumber(fieldnum.Value_StructValue)
- val := m.NewField(fd)
- if err := o.unmarshalStruct(val.Message()); err != nil {
+ case json.ObjectOpen:
+ fd = m.Descriptor().Fields().ByNumber(fieldnum.Value_StructValue)
+ val = m.NewField(fd)
+ if err := d.unmarshalStruct(val.Message()); err != nil {
return err
}
- m.Set(fd, val)
- case json.StartArray:
- fd := m.Descriptor().Fields().ByNumber(fieldnum.Value_ListValue)
- val := m.NewField(fd)
- if err := o.unmarshalListValue(val.Message()); err != nil {
+ case json.ArrayOpen:
+ fd = m.Descriptor().Fields().ByNumber(fieldnum.Value_ListValue)
+ val = m.NewField(fd)
+ if err := d.unmarshalListValue(val.Message()); err != nil {
return err
}
- m.Set(fd, val)
default:
- jval, err := o.decoder.Read()
- if err != nil {
- return err
- }
- return unexpectedJSONError{jval}
+ return d.newError(tok.Pos(), "invalid google.protobuf.Value: %v", tok.RawString())
}
+
+ m.Set(fd, val)
return nil
}
@@ -628,7 +628,7 @@
maxSecondsInDuration = 315576000000
)
-func (o MarshalOptions) marshalDuration(m pref.Message) error {
+func (e encoder) marshalDuration(m pref.Message) error {
fds := m.Descriptor().Fields()
fdSeconds := fds.ByNumber(fieldnum.Duration_Seconds)
fdNanos := fds.ByNumber(fieldnum.Duration_Nanos)
@@ -659,28 +659,27 @@
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, ".000")
- o.encoder.WriteString(x + "s")
+ e.WriteString(x + "s")
return nil
}
-func (o UnmarshalOptions) unmarshalDuration(m pref.Message) error {
- jval, err := o.decoder.Read()
+func (d decoder) unmarshalDuration(m pref.Message) error {
+ tok, err := d.Read()
if err != nil {
return err
}
- if jval.Type() != json.String {
- return unexpectedJSONError{jval}
+ if tok.Kind() != json.String {
+ return d.unexpectedTokenError(tok)
}
- input := jval.String()
- secs, nanos, ok := parseDuration(input)
+ secs, nanos, ok := parseDuration(tok.ParsedString())
if !ok {
- return errors.New("%s: invalid duration value %q", m.Descriptor().FullName(), input)
+ return d.newError(tok.Pos(), "invalid google.protobuf.Duration value %v", tok.RawString())
}
// Validate seconds. No need to validate nanos because parseDuration would
// have covered that already.
if secs < -maxSecondsInDuration || secs > maxSecondsInDuration {
- return errors.New("%s: out of range %q", m.Descriptor().FullName(), input)
+ return d.newError(tok.Pos(), "google.protobuf.Duration value out of range: %v", tok.RawString())
}
fds := m.Descriptor().Fields()
@@ -820,7 +819,7 @@
minTimestampSeconds = -62135596800
)
-func (o MarshalOptions) marshalTimestamp(m pref.Message) error {
+func (e encoder) marshalTimestamp(m pref.Message) error {
fds := m.Descriptor().Fields()
fdSeconds := fds.ByNumber(fieldnum.Timestamp_Seconds)
fdNanos := fds.ByNumber(fieldnum.Timestamp_Nanos)
@@ -842,29 +841,28 @@
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, ".000")
- o.encoder.WriteString(x + "Z")
+ e.WriteString(x + "Z")
return nil
}
-func (o UnmarshalOptions) unmarshalTimestamp(m pref.Message) error {
- jval, err := o.decoder.Read()
+func (d decoder) unmarshalTimestamp(m pref.Message) error {
+ tok, err := d.Read()
if err != nil {
return err
}
- if jval.Type() != json.String {
- return unexpectedJSONError{jval}
+ if tok.Kind() != json.String {
+ return d.unexpectedTokenError(tok)
}
- input := jval.String()
- t, err := time.Parse(time.RFC3339Nano, input)
+ t, err := time.Parse(time.RFC3339Nano, tok.ParsedString())
if err != nil {
- return errors.New("%s: invalid timestamp value %q", m.Descriptor().FullName(), input)
+ return d.newError(tok.Pos(), "invalid google.protobuf.Timestamp value %v", tok.RawString())
}
// Validate seconds. No need to validate nanos because time.Parse would have
// covered that already.
secs := t.Unix()
if secs < minTimestampSeconds || secs > maxTimestampSeconds {
- return errors.New("%s: out of range %q", m.Descriptor().FullName(), input)
+ return d.newError(tok.Pos(), "google.protobuf.Timestamp value out of range: %v", tok.RawString())
}
fds := m.Descriptor().Fields()
@@ -881,7 +879,7 @@
// lower-camel naming conventions. Encoding should fail if the path name would
// end up differently after a round-trip.
-func (o MarshalOptions) marshalFieldMask(m pref.Message) error {
+func (e encoder) marshalFieldMask(m pref.Message) error {
fd := m.Descriptor().Fields().ByNumber(fieldnum.FieldMask_Paths)
list := m.Get(fd).List()
paths := make([]string, 0, list.Len())
@@ -896,19 +894,19 @@
paths = append(paths, cc)
}
- o.encoder.WriteString(strings.Join(paths, ","))
+ e.WriteString(strings.Join(paths, ","))
return nil
}
-func (o UnmarshalOptions) unmarshalFieldMask(m pref.Message) error {
- jval, err := o.decoder.Read()
+func (d decoder) unmarshalFieldMask(m pref.Message) error {
+ tok, err := d.Read()
if err != nil {
return err
}
- if jval.Type() != json.String {
- return unexpectedJSONError{jval}
+ if tok.Kind() != json.String {
+ return d.unexpectedTokenError(tok)
}
- str := strings.TrimSpace(jval.String())
+ str := strings.TrimSpace(tok.ParsedString())
if str == "" {
return nil
}