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: &timestamppb.Timestamp{},
@@ -1845,7 +1850,7 @@
 		desc:         "Timestamp above max value",
 		inputMessage: &timestamppb.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: &timestamppb.Timestamp{},
@@ -1855,12 +1860,12 @@
 		desc:         "Timestamp below min value",
 		inputMessage: &timestamppb.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: &timestamppb.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
 	}