Repository: segmentio/encoding (go)

Issue:
Unmarshaling into an interface{} instead of structs
I'm trying to work with Amazon S3 Bucket Policies, which are polymorphic enough that not even the AWS SDK team has attempted to model the rules for them with Go structs. So I'm trying to decode the S3 Bucket Policy JSON into an `interface{}`.

Here's some rough code, extracted from my project.

```go
type S3BucketPolicy interface{}

var bucketPolicy S3BucketPolicy

request := client.GetBucketPolicyRequest(&s3.GetBucketPolicyInput{
    Bucket: &bucketname,
})

// Errors are acceptable
response, _ := request.Send(ctx)

if response != nil && response.Policy != nil && *response.Policy != emptyString {
    if err := json.Unmarshal([]byte(*response.Policy), &bucketPolicy); err != nil {
        exitErrorf(err)
    }
}
```

Using `encoding/json`, this works just fine. However, after dropping-in `github.com/segmentio/encoding/json` as a replacement, I receive the following error:

```
Error: json: cannot unmarshal {"Version":"2012-10-17","Statement":[{"Sid":"AWSCloudTrailWrite","Effect":"Allow","Principal":{"Service":"cloudtrail.amazonaws.com"},"Action":"s3:PutObject","Resource":"arn:aws:s3:::BUCKET/AWSLogs/ACCOUNTID/*","Condition":{"StringEquals":{"s3:x-amz-acl":"bucket-owner-full-control"}}},{"Sid":"AWSLogDeliveryWrite","Effect":"Allow","Principal":{"Service":"delivery.logs.amazonaws.com"},"Action":"s3:PutObject","Resource":"arn:aws:s3:::BUCKET/AWSLogs/ACCOUNTID/*","Condition":{"StringEquals":{"s3:x-amz-acl":"bucket-owner-full-control"}}},{"Sid":"AWSLogDeliveryAclCheck","Effect":"Allow","Principal":{"Service":"delivery.logs.amazonaws.com"},"Action":"s3:GetBucketAcl","Resource":"arn:aws:s3:::BUCKET"},{"Sid":"AWSELBWrite","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::ACCOUNTID:root"},"Action":"s3:PutObject","Resource":"arn:aws:s3:::BUCKET/*"},{"Sid":"AWSAclCheck","Effect":"Allow","Principal":{"Service":"cloudtrail.amazonaws.com","AWS":"arn:aws:iam::ACCOUNTID:user/logs"},"Action":"s3:GetBucketAcl","Resource":"arn:aws:s3:::BUCKET"},{"Sid":"AWSRedshiftWrite","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::ACCOUNTID:user/logs"},"Action":"s3:PutObject","Resource":"arn:aws:s3:::BUCKET/*"}]} into Go value of type main.S3BucketPolicy
```

Here is the JSON (pretty printed), which is valid:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AWSCloudTrailWrite",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudtrail.amazonaws.com"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::BUCKET/AWSLogs/ACCOUNTID/*",
      "Condition": {
        "StringEquals": {
          "s3:x-amz-acl": "bucket-owner-full-control"
        }
      }
    },
    {
      "Sid": "AWSLogDeliveryWrite",
      "Effect": "Allow",
      "Principal": {
        "Service": "delivery.logs.amazonaws.com"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::BUCKET/AWSLogs/ACCOUNTID/*",
      "Condition": {
        "StringEquals": {
          "s3:x-amz-acl": "bucket-owner-full-control"
        }
      }
    },
    {
      "Sid": "AWSLogDeliveryAclCheck",
      "Effect": "Allow",
      "Principal": {
        "Service": "delivery.logs.amazonaws.com"
      },
      "Action": "s3:GetBucketAcl",
      "Resource": "arn:aws:s3:::BUCKET"
    },
    {
      "Sid": "AWSELBWrite",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::ACCOUNTID:root"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::BUCKET/*"
    },
    {
      "Sid": "AWSAclCheck",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudtrail.amazonaws.com",
        "AWS": "arn:aws:iam::ACCOUNTID:user/logs"
      },
      "Action": "s3:GetBucketAcl",
      "Resource": "arn:aws:s3:::BUCKET"
    },
    {
      "Sid": "AWSRedshiftWrite",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::ACCOUNTID:user/logs"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::BUCKET/*"
    }
  ]
}
```

Is this use-case supported?

Pull request:
fix issue 26
Fixes #26 

Unmarshaling to named empty interfaces didn't work properly (e.g. `type I interface{}`), this PR should fix it.


Patch:
diff --git a/json/codec.go b/json/codec.go
index e06e72b..1ef84f5 100644
--- a/json/codec.go
+++ b/json/codec.go
@@ -156,7 +156,7 @@ func constructCodec(t reflect.Type, seen map[reflect.Type]*structType, canAddr b
 		c = codec{encode: encoder.encodeString, decode: decoder.decodeString}
 
 	case reflect.Interface:
-		c = codec{encode: encoder.encodeInterface, decode: constructNonEmptyInterfaceDecoderFunc(t)}
+		c = codec{encode: encoder.encodeInterface, decode: constructMaybeEmptyInterfaceDecoderFunc(t)}
 
 	case reflect.Array:
 		c = constructArrayCodec(t, seen, canAddr)
@@ -711,9 +711,9 @@ func constructPointerDecodeFunc(t reflect.Type, decode decodeFunc) decodeFunc {
 	}
 }
 
-func constructNonEmptyInterfaceDecoderFunc(t reflect.Type) decodeFunc {
+func constructMaybeEmptyInterfaceDecoderFunc(t reflect.Type) decodeFunc {
 	return func(d decoder, b []byte, p unsafe.Pointer) ([]byte, error) {
-		return d.decodeNonEmptyInterface(b, p, t)
+		return d.decodeMaybeEmptyInterface(b, p, t)
 	}
 }
 
diff --git a/json/decode.go b/json/decode.go
index 14010d4..e01b444 100644
--- a/json/decode.go
+++ b/json/decode.go
@@ -1087,7 +1087,7 @@ 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) {
+func (d decoder) decodeMaybeEmptyInterface(b []byte, p unsafe.Pointer, t reflect.Type) ([]byte, error) {
 	if hasNullPrefix(b) {
 		*(*interface{})(p) = nil
 		return b[4:], nil
@@ -1097,6 +1097,8 @@ func (d decoder) decodeNonEmptyInterface(b []byte, p unsafe.Pointer, t reflect.T
 		if e := x.Elem(); e.Kind() == reflect.Ptr {
 			return Parse(b, e.Interface(), d.flags)
 		}
+	} else if t.NumMethod() == 0 { // empty interface
+		return Parse(b, (*interface{})(p), d.flags)
 	}
 
 	return d.decodeUnmarshalTypeError(b, p, t)


Test patch:
diff --git a/json/json_test.go b/json/json_test.go
index c197a5c..8c430de 100644
--- a/json/json_test.go
+++ b/json/json_test.go
@@ -1464,3 +1464,14 @@ func TestGithubIssue23(t *testing.T) {
 		}
 	})
 }
+
+func TestGithubIssue26(t *testing.T) {
+	type interfaceType interface{}
+
+	var value interfaceType
+	var data = []byte(`{}`)
+
+	if err := Unmarshal(data, &value); err != nil {
+		t.Error(err)
+	}
+}