Repository: segmentio/encoding (go)

Issue:
Marshalling fails with Interface members when they are nil
I was migrating some marshalling code to use this library over the standard library (thank you for making it!) and encountered this error. Note that the stdlib reports a nil error with data, while `segmentio/encoding/json` returns an error. i

Minimal repro:
```go
package main

import (
	"fmt"

	"github.com/segmentio/encoding/json"
)

type segmentJSON struct {
	Stringer fmt.Stringer
	Field    int `json:"MyInt"`
}

func main() {
	fmt.Println("segment-error")
	js := segmentJSON{Field: 2}
	fmt.Println(json.Marshal(js))
}
```

Output when using `encoding/json`:
```bash
❯ go run main.go
segment-error
[123 34 83 116 114 105 110 103 101 114 34 58 110 117 108 108 44 34 77 121 73 110 116 34 58 50 125] <nil>
```

with `github.com/segmentio/encoding/json`:
```
❯ go run main.go
segment-error
[] json: unsupported type: fmt.Stringer
```

Pull request:
fix issue 13
Fixes #13 

The `encoding/json` package does a bit of special magic when it comes to decoding into non-empty interface types:
- if the underlying value is nil it always accepts the `null` value but refuses everything else
- if the underlying value is a non-nill pointer type it will attempt to decode JSON into the value


Patch:
diff --git a/json/codec.go b/json/codec.go
index db46d01..2021f73 100644
--- a/json/codec.go
+++ b/json/codec.go
@@ -155,6 +155,9 @@ func constructCodec(t reflect.Type, seen map[reflect.Type]*structType) (c codec)
 	case reflect.String:
 		c = codec{encode: encoder.encodeString, decode: decoder.decodeString}
 
+	case reflect.Interface:
+		c = codec{encode: encoder.encodeInterface, decode: constructNonEmptyInterfaceDecoderFunc(t)}
+
 	case reflect.Array:
 		c = constructArrayCodec(t, seen)
 
@@ -171,7 +174,7 @@ func constructCodec(t reflect.Type, seen map[reflect.Type]*structType) (c codec)
 		c = constructPointerCodec(t, seen)
 
 	default:
-		c = constructUnmarshalTypeErrorCodec(t)
+		c = constructUnsupportedTypeCodec(t)
 	}
 
 	p := reflect.PtrTo(t)
@@ -388,7 +391,7 @@ func constructMapCodec(t reflect.Type, seen map[reflect.Type]*structType) codec
 			}
 
 		default:
-			return constructUnmarshalTypeErrorCodec(t)
+			return constructUnsupportedTypeCodec(t)
 		}
 	}
 
@@ -708,20 +711,26 @@ func constructPointerDecodeFunc(t reflect.Type, decode decodeFunc) decodeFunc {
 	}
 }
 
-func constructUnmarshalTypeErrorCodec(t reflect.Type) codec {
+func constructNonEmptyInterfaceDecoderFunc(t reflect.Type) decodeFunc {
+	return func(d decoder, b []byte, p unsafe.Pointer) ([]byte, error) {
+		return d.decodeNonEmptyInterface(b, p, t)
+	}
+}
+
+func constructUnsupportedTypeCodec(t reflect.Type) codec {
 	return codec{
-		encode: constructUnmarshalTypeErrorEncodeFunc(t),
-		decode: constructUnmarshalTypeErrorDecodeFunc(t),
+		encode: constructUnsupportedTypeEncodeFunc(t),
+		decode: constructUnsupportedTypeDecodeFunc(t),
 	}
 }
 
-func constructUnmarshalTypeErrorEncodeFunc(t reflect.Type) encodeFunc {
+func constructUnsupportedTypeEncodeFunc(t reflect.Type) encodeFunc {
 	return func(e encoder, b []byte, p unsafe.Pointer) ([]byte, error) {
-		return e.encodeUnsupportedType(b, p, t)
+		return e.encodeUnsupportedTypeError(b, p, t)
 	}
 }
 
-func constructUnmarshalTypeErrorDecodeFunc(t reflect.Type) decodeFunc {
+func constructUnsupportedTypeDecodeFunc(t reflect.Type) decodeFunc {
 	return func(d decoder, b []byte, p unsafe.Pointer) ([]byte, error) {
 		return d.decodeUnmarshalTypeError(b, p, t)
 	}
diff --git a/json/decode.go b/json/decode.go
index 66645ac..e42b4ce 100644
--- a/json/decode.go
+++ b/json/decode.go
@@ -1085,6 +1085,21 @@ func (d decoder) decodeInterface(b []byte, p unsafe.Pointer) ([]byte, error) {
 	return b, nil
 }
 
+func (d decoder) decodeNonEmptyInterface(b []byte, p unsafe.Pointer, t reflect.Type) ([]byte, error) {
+	if hasNullPrefix(b) {
+		*(*interface{})(p) = nil
+		return b[4:], nil
+	}
+
+	if x := reflect.NewAt(t, p).Elem(); !x.IsNil() {
+		if e := x.Elem(); e.Kind() == reflect.Ptr {
+			return Parse(b, e.Interface(), d.flags)
+		}
+	}
+
+	return d.decodeUnmarshalTypeError(b, p, t)
+}
+
 func (d decoder) decodeUnmarshalTypeError(b []byte, p unsafe.Pointer, t reflect.Type) ([]byte, error) {
 	v, b, err := parseValue(b)
 	if err != nil {
diff --git a/json/encode.go b/json/encode.go
index 8f0096d..be9d5c0 100644
--- a/json/encode.go
+++ b/json/encode.go
@@ -592,7 +592,7 @@ func (e encoder) encodeInterface(b []byte, p unsafe.Pointer) ([]byte, error) {
 	return Append(b, *(*interface{})(p), e.flags)
 }
 
-func (e encoder) encodeUnsupportedType(b []byte, p unsafe.Pointer, t reflect.Type) ([]byte, error) {
+func (e encoder) encodeUnsupportedTypeError(b []byte, p unsafe.Pointer, t reflect.Type) ([]byte, error) {
 	return b, &UnsupportedTypeError{Type: t}
 }
 


Test patch:
diff --git a/json/json_test.go b/json/json_test.go
index fe525c9..2517617 100644
--- a/json/json_test.go
+++ b/json/json_test.go
@@ -1204,3 +1204,48 @@ func TestGithubIssue11(t *testing.T) {
 		t.Log(s)
 	}
 }
+
+type Issue13 struct {
+	Stringer fmt.Stringer
+	Field    int `json:"MyInt"`
+}
+
+type S string
+
+func (s S) String() string { return string(s) }
+
+func TestGithubIssue13(t *testing.T) {
+	// https://github.com/segmentio/encoding/issues/13
+	v := Issue13{}
+
+	b, err := Marshal(v)
+	if err != nil {
+		t.Error("unexpected errror:", err)
+	} else {
+		t.Log(string(b))
+	}
+
+	v = Issue13{Stringer: S("")}
+	if err := Unmarshal([]byte(`{"Stringer":null}`), &v); err != nil {
+		t.Error("unexpected error:", err)
+	}
+	if v.Stringer != nil {
+		t.Error("Stringer field was not overwritten")
+	}
+
+	v = Issue13{}
+	if err := Unmarshal([]byte(`{"Stringer":"whatever"}`), &v); err == nil {
+		t.Error("expected error but decoding string value into nil fmt.Stringer but got <nil>")
+	}
+
+	v = Issue13{Stringer: S("")}
+	if err := Unmarshal([]byte(`{"Stringer":"whatever"}`), &v); err == nil {
+		t.Error("expected error but decoding string value into non-pointer fmt.Stringer but got <nil>")
+	}
+
+	s := S("")
+	v = Issue13{Stringer: &s}
+	if err := Unmarshal([]byte(`{"Stringer":"whatever"}`), &v); err != nil {
+		t.Error("unexpected error decoding string value into pointer fmt.Stringer:", err)
+	}
+}