]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json: marshal with null when RawMessage is nil
authorJoe Tsai <joetsai@digital-static.net>
Tue, 1 Nov 2016 00:34:42 +0000 (17:34 -0700)
committerJoe Tsai <thebrokentoaster@gmail.com>
Tue, 1 Nov 2016 05:42:33 +0000 (05:42 +0000)
This CL expands upon a change made in (http://golang.org/cl/21811)
to ensure that a nil RawMessage gets serialized as "null" instead of
being a nil slice.

The added check only triggers when the RawMessage is nil. We do not
handle the case when the RawMessage is non-nil, but empty.

Fixes #17704
Updates #14493

Change-Id: I0fbebcdd81f7466c5b78c94953afc897f162ceb4
Reviewed-on: https://go-review.googlesource.com/32472
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/encoding/json/encode_test.go
src/encoding/json/stream.go

index 507581feeda3b6bc8c8f47d21d023f87cf332c69..28feeeffdf8e3cd1791b5169d2fb2879305df265 100644 (file)
@@ -421,32 +421,6 @@ func TestStringBytes(t *testing.T) {
        }
 }
 
-func TestIssue6458(t *testing.T) {
-       type Foo struct {
-               M RawMessage
-       }
-       x := Foo{RawMessage(`"foo"`)}
-
-       b, err := Marshal(&x)
-       if err != nil {
-               t.Fatal(err)
-       }
-       if want := `{"M":"foo"}`; string(b) != want {
-               t.Errorf("Marshal(&x) = %#q; want %#q", b, want)
-       }
-
-       b, err = Marshal(x)
-       if err != nil {
-               t.Fatal(err)
-       }
-
-       // Until Go 1.8, this generated `{"M":"ImZvbyI="}`.
-       // See https://github.com/golang/go/issues/14493#issuecomment-255857318
-       if want := `{"M":"foo"}`; string(b) != want {
-               t.Errorf("Marshal(x) = %#q; want %#q", b, want)
-       }
-}
-
 func TestIssue10281(t *testing.T) {
        type Foo struct {
                N Number
@@ -721,12 +695,102 @@ func TestMarshalFloat(t *testing.T) {
 }
 
 func TestMarshalRawMessageValue(t *testing.T) {
-       const val = "\"some value\""
-       b, err := Marshal(RawMessage(val))
-       if err != nil {
-               t.Fatal(err)
-       }
-       if string(b) != val {
-               t.Errorf("got %q; want %q", b, val)
+       type (
+               T1 struct {
+                       M RawMessage `json:",omitempty"`
+               }
+               T2 struct {
+                       M *RawMessage `json:",omitempty"`
+               }
+       )
+
+       var (
+               rawNil   = RawMessage(nil)
+               rawEmpty = RawMessage([]byte{})
+               rawText  = RawMessage([]byte(`"foo"`))
+       )
+
+       tests := []struct {
+               in   interface{}
+               want string
+               ok   bool
+       }{
+               // Test with nil RawMessage.
+               {rawNil, "null", true},
+               {&rawNil, "null", true},
+               {[]interface{}{rawNil}, "[null]", true},
+               {&[]interface{}{rawNil}, "[null]", true},
+               {[]interface{}{&rawNil}, "[null]", true},
+               {&[]interface{}{&rawNil}, "[null]", true},
+               {struct{ M RawMessage }{rawNil}, `{"M":null}`, true},
+               {&struct{ M RawMessage }{rawNil}, `{"M":null}`, true},
+               {struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true},
+               {&struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true},
+               {map[string]interface{}{"M": rawNil}, `{"M":null}`, true},
+               {&map[string]interface{}{"M": rawNil}, `{"M":null}`, true},
+               {map[string]interface{}{"M": &rawNil}, `{"M":null}`, true},
+               {&map[string]interface{}{"M": &rawNil}, `{"M":null}`, true},
+               {T1{rawNil}, "{}", true},
+               {T2{&rawNil}, `{"M":null}`, true},
+               {&T1{rawNil}, "{}", true},
+               {&T2{&rawNil}, `{"M":null}`, true},
+
+               // Test with empty, but non-nil, RawMessage.
+               {rawEmpty, "", false},
+               {&rawEmpty, "", false},
+               {[]interface{}{rawEmpty}, "", false},
+               {&[]interface{}{rawEmpty}, "", false},
+               {[]interface{}{&rawEmpty}, "", false},
+               {&[]interface{}{&rawEmpty}, "", false},
+               {struct{ X RawMessage }{rawEmpty}, "", false},
+               {&struct{ X RawMessage }{rawEmpty}, "", false},
+               {struct{ X *RawMessage }{&rawEmpty}, "", false},
+               {&struct{ X *RawMessage }{&rawEmpty}, "", false},
+               {map[string]interface{}{"nil": rawEmpty}, "", false},
+               {&map[string]interface{}{"nil": rawEmpty}, "", false},
+               {map[string]interface{}{"nil": &rawEmpty}, "", false},
+               {&map[string]interface{}{"nil": &rawEmpty}, "", false},
+               {T1{rawEmpty}, "{}", true},
+               {T2{&rawEmpty}, "", false},
+               {&T1{rawEmpty}, "{}", true},
+               {&T2{&rawEmpty}, "", false},
+
+               // Test with RawMessage with some text.
+               //
+               // The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo".
+               // This behavior was intentionally changed in Go 1.8.
+               // See https://github.com/golang/go/issues/14493#issuecomment-255857318
+               {rawText, `"foo"`, true}, // Issue6458
+               {&rawText, `"foo"`, true},
+               {[]interface{}{rawText}, `["foo"]`, true},  // Issue6458
+               {&[]interface{}{rawText}, `["foo"]`, true}, // Issue6458
+               {[]interface{}{&rawText}, `["foo"]`, true},
+               {&[]interface{}{&rawText}, `["foo"]`, true},
+               {struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458
+               {&struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true},
+               {struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true},
+               {&struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true},
+               {map[string]interface{}{"M": rawText}, `{"M":"foo"}`, true},  // Issue6458
+               {&map[string]interface{}{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458
+               {map[string]interface{}{"M": &rawText}, `{"M":"foo"}`, true},
+               {&map[string]interface{}{"M": &rawText}, `{"M":"foo"}`, true},
+               {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458
+               {T2{&rawText}, `{"M":"foo"}`, true},
+               {&T1{rawText}, `{"M":"foo"}`, true},
+               {&T2{&rawText}, `{"M":"foo"}`, true},
+       }
+
+       for i, tt := range tests {
+               b, err := Marshal(tt.in)
+               if ok := (err == nil); ok != tt.ok {
+                       if err != nil {
+                               t.Errorf("test %d, unexpected failure: %v", i, err)
+                       } else {
+                               t.Errorf("test %d, unexpected success", i)
+                       }
+               }
+               if got := string(b); got != tt.want {
+                       t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want)
+               }
        }
 }
index 4c350fdd5ea78e1f090c4ade7140f27d4b831465..95e30ce36d52e5fc244ef70f58648498e8871c88 100644 (file)
@@ -248,6 +248,9 @@ type RawMessage []byte
 
 // MarshalJSON returns m as the JSON encoding of m.
 func (m RawMessage) MarshalJSON() ([]byte, error) {
+       if m == nil {
+               return []byte("null"), nil
+       }
        return m, nil
 }