encoding/protojson: add random whitespaces in encoding output

This is meant to deter users from doing byte for byte comparison.

Change-Id: If005d2dc1eba45eaa4254171d2f247820db109e4
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/194037
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/encoding/protojson/encode.go b/encoding/protojson/encode.go
index 1662e40..5f383fe 100644
--- a/encoding/protojson/encode.go
+++ b/encoding/protojson/encode.go
@@ -20,6 +20,8 @@
 )
 
 // Marshal writes the given proto.Message in JSON format using default options.
+// Do not depend on the output of being stable. It may change over time across
+// different versions of the program.
 func Marshal(m proto.Message) ([]byte, error) {
 	return MarshalOptions{}.Marshal(m)
 }
@@ -71,7 +73,8 @@
 }
 
 // Marshal marshals the given proto.Message in the JSON format using options in
-// MarshalOptions.
+// MarshalOptions. Do not depend on the output being stable. It may change over
+// time across different versions of the program.
 func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
 	var err error
 	o.encoder, err = json.NewEncoder(o.Indent)
diff --git a/encoding/protojson/encode_test.go b/encoding/protojson/encode_test.go
index 9a1c86e..38e60e7 100644
--- a/encoding/protojson/encode_test.go
+++ b/encoding/protojson/encode_test.go
@@ -11,6 +11,7 @@
 
 	"github.com/google/go-cmp/cmp"
 	"google.golang.org/protobuf/encoding/protojson"
+	"google.golang.org/protobuf/internal/detrand"
 	"google.golang.org/protobuf/internal/encoding/pack"
 	"google.golang.org/protobuf/internal/flags"
 	pimpl "google.golang.org/protobuf/internal/impl"
@@ -28,6 +29,9 @@
 	"google.golang.org/protobuf/types/known/wrapperspb"
 )
 
+// Disable detrand to enable direct comparisons on outputs.
+func init() { detrand.Disable() }
+
 func TestMarshal(t *testing.T) {
 	tests := []struct {
 		desc    string
diff --git a/internal/encoding/json/encode.go b/internal/encoding/json/encode.go
index 39a5014..741f34f 100644
--- a/internal/encoding/json/encode.go
+++ b/internal/encoding/json/encode.go
@@ -8,6 +8,7 @@
 	"strconv"
 	"strings"
 
+	"google.golang.org/protobuf/internal/detrand"
 	"google.golang.org/protobuf/internal/errors"
 )
 
@@ -132,6 +133,11 @@
 		if e.lastType&(Null|Bool|Number|String|EndObject|EndArray) != 0 &&
 			next&(Name|Null|Bool|Number|String|StartObject|StartArray) != 0 {
 			e.out = append(e.out, ',')
+			// For single-line output, add a random extra space after each
+			// comma to make output unstable.
+			if detrand.Bool() {
+				e.out = append(e.out, ' ')
+			}
 		}
 		return
 	}
@@ -160,5 +166,10 @@
 
 	case e.lastType&Name != 0:
 		e.out = append(e.out, ' ')
+		// For multi-line output, add a random extra space after key: to make
+		// output unstable.
+		if detrand.Bool() {
+			e.out = append(e.out, ' ')
+		}
 	}
 }
diff --git a/internal/encoding/json/encode_test.go b/internal/encoding/json/encode_test.go
index b98f98b..99ecb1f 100644
--- a/internal/encoding/json/encode_test.go
+++ b/internal/encoding/json/encode_test.go
@@ -11,9 +11,13 @@
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
+	"google.golang.org/protobuf/internal/detrand"
 	"google.golang.org/protobuf/internal/encoding/json"
 )
 
+// Disable detrand to enable direct comparisons on outputs.
+func init() { detrand.Disable() }
+
 // splitLines is a cmpopts.Option for comparing strings with line breaks.
 var splitLines = cmpopts.AcyclicTransformer("SplitLines", func(s string) []string {
 	return strings.Split(s, "\n")