]> Cypherpunks repositories - gostls13.git/commitdiff
json: skip nil in UnmarshalJSON and (for symmetry) MarshalJSON
authorRuss Cox <rsc@golang.org>
Mon, 19 Sep 2011 15:50:41 +0000 (11:50 -0400)
committerRuss Cox <rsc@golang.org>
Mon, 19 Sep 2011 15:50:41 +0000 (11:50 -0400)
R=dsymonds, r
CC=golang-dev
https://golang.org/cl/5050049

src/pkg/json/decode.go
src/pkg/json/encode.go
src/pkg/json/stream_test.go

index 268747dc60cc9dcf2c58f78422ca20f9cf07f363..e0cc408cbf888955cde5b31133910b6670375864 100644 (file)
@@ -22,16 +22,11 @@ import (
 // Unmarshal parses the JSON-encoded data and stores the result
 // in the value pointed to by v.
 //
-// Unmarshal traverses the value v recursively.
-// If an encountered value implements the Unmarshaler interface,
-// Unmarshal calls its UnmarshalJSON method with a well-formed
-// JSON encoding.
-//
-// Otherwise, Unmarshal uses the inverse of the encodings that
+// Unmarshal uses the inverse of the encodings that
 // Marshal uses, allocating maps, slices, and pointers as necessary,
 // with the following additional rules:
 //
-// To unmarshal a JSON value into a nil interface value, the
+// To unmarshal JSON into a nil interface value, the
 // type stored in the interface value is one of:
 //
 //     bool, for JSON booleans
@@ -41,6 +36,12 @@ import (
 //     map[string]interface{}, for JSON objects
 //     nil for JSON null
 //
+// To unmarshal JSON into a pointer, Unmarshal first handles the case of
+// the JSON being the JSON literal null.  In that case, Unmarshal sets
+// the pointer to nil.  Otherwise, Unmarshal unmarshals the JSON into
+// the value pointed at by the pointer.  If the pointer is nil, Unmarshal
+// allocates a new value for it to point to.
+//
 // If a JSON value is not appropriate for a given target type,
 // or if a JSON number overflows the target type, Unmarshal
 // skips that field and completes the unmarshalling as best it can.
@@ -250,8 +251,8 @@ func (d *decodeState) value(v reflect.Value) {
 // indirect walks down v allocating pointers as needed,
 // until it gets to a non-pointer.
 // if it encounters an Unmarshaler, indirect stops and returns that.
-// if wantptr is true, indirect stops at the last pointer.
-func (d *decodeState) indirect(v reflect.Value, wantptr bool) (Unmarshaler, reflect.Value) {
+// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
+func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, reflect.Value) {
        // If v is a named type and is addressable,
        // start with its address, so that if the type has pointer methods,
        // we find them.
@@ -277,7 +278,7 @@ func (d *decodeState) indirect(v reflect.Value, wantptr bool) (Unmarshaler, refl
                        break
                }
 
-               if pv.Elem().Kind() != reflect.Ptr && wantptr && pv.CanSet() && !isUnmarshaler {
+               if pv.Elem().Kind() != reflect.Ptr && decodingNull && pv.CanSet() {
                        return nil, pv
                }
                if pv.IsNil() {
index 71d927d638f96679eba7a22b05a3e96b7e42de0d..46abe4360ed6c59d12baf8df4507ba16abd132e0 100644 (file)
@@ -24,8 +24,11 @@ import (
 // Marshal returns the JSON encoding of v.
 //
 // Marshal traverses the value v recursively.
-// If an encountered value implements the Marshaler interface,
-// Marshal calls its MarshalJSON method to produce JSON.
+// If an encountered value implements the Marshaler interface
+// and is not a nil pointer, Marshal calls its MarshalJSON method
+// to produce JSON.  The nil pointer exception is not strictly necessary
+// but mimics a similar, necessary exception in the behavior of
+// UnmarshalJSON.
 //
 // Otherwise, Marshal uses the following type-dependent default encodings:
 //
@@ -245,7 +248,7 @@ func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) {
                return
        }
 
-       if j, ok := v.Interface().(Marshaler); ok {
+       if j, ok := v.Interface().(Marshaler); ok && (v.Kind() != reflect.Ptr || !v.IsNil()) {
                b, err := j.MarshalJSON()
                if err == nil {
                        // copy JSON into buffer, checking validity.
index 6ddaed9fe8f2bf11de018acda8739a6d63cd0bec..ce5a7e6d65625ea52b0d5c340833eb30e83d5372 100644 (file)
@@ -120,3 +120,28 @@ func TestRawMessage(t *testing.T) {
                t.Fatalf("Marshal: have %#q want %#q", b, msg)
        }
 }
+
+func TestNullRawMessage(t *testing.T) {
+       // TODO(rsc): Should not need the * in *RawMessage
+       var data struct {
+               X  float64
+               Id *RawMessage
+               Y  float32
+       }
+       data.Id = new(RawMessage)
+       const msg = `{"X":0.1,"Id":null,"Y":0.2}`
+       err := Unmarshal([]byte(msg), &data)
+       if err != nil {
+               t.Fatalf("Unmarshal: %v", err)
+       }
+       if data.Id != nil {
+               t.Fatalf("Raw mismatch: have non-nil, want nil")
+       }
+       b, err := Marshal(&data)
+       if err != nil {
+               t.Fatalf("Marshal: %v", err)
+       }
+       if string(b) != msg {
+               t.Fatalf("Marshal: have %#q want %#q", b, msg)
+       }
+}