-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix "Panic: reflect.Value.Interface: cannot return value obtained fro… #5
Conversation
…m unexported field or method"
… like unexported fields. The deepequal package had the same problem, so I copied their fix, seems to really fix this time. https://github.com/juju/testing/blob/master/checkers/deepequal.go#L269
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your PR! 👍
I added some nitpick comments. What I'm a bit uneasy about is this bypassing of CanInterface.
Can you elaborate why this is needed? Should we rather open an issue for Go to fix CanInterface?
Is the bypassCanInterface
production-ready code?
// from inside an unexported field. | ||
// See https://code.google.com/p/go/issues/detail?id=8965 | ||
// for a possible future alternative to this hack. | ||
// from https://github.com/juju/testing/blob/master/checkers/deepequal.go |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This enables us to call fmt.Printf on a value even if it's derived
We don't use it for fmt.Printf
or am I missing something? I assume this is a verbatim copy of the comment in deepequal. I think we should adapt that comment to match our case.
Also this sentence is wrong as it's in Go already:
for a possible future alternative to this hack.
flagRO1p3 = 1 << 0 | ||
|
||
// The value of flagRO from Go 1.4. | ||
flagRO1p4 = 1 << 5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unsure if we should not just use this value because we don't test sheriff in older versions than 1.6 anyway.
@@ -464,3 +464,32 @@ func TestMarshal_EmbeddedFieldEmpty(t *testing.T) { | |||
|
|||
assert.Equal(t, string(expected), string(actual)) | |||
} | |||
|
|||
type TestInlineStruct struct { | |||
tableName struct{} `json:"-"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go vet
says this is an error because json tag in unexported field name. The test must actually test this specific case, right?
./sheriff_test.go:469: struct field tableName has json tag but is not exported
(https://travis-ci.org/liip/sheriff/jobs/297684025)
Seems like we need to switch to specific flags only.. I can handle this later on.
Well, to be fair, I really didn't understand what the source from deepequal.go was doing, as I am not too versed on the reflect package.
To solve these problems, I tried calling ".IsValid()" which is supposed to tell if a value is "the zero value". But for the "tableName" field, IsValid=true and CanInterface=false, so it didn't work in all cases. I tried all kinds of combinations, but failed to find one valid for all cases. Looking around, this seems to be an inconsitency (maybe bug) of the reflect package. The TestInlineStruct in my sheriff_test.go contains all the problems I appointed, maybe it can be easier for you to just copy this test in your branch and try to fix it. |
I made another try to fix this, it solved the 2 problems I had, see if you think this is better. https://github.com/RangelReale/sheriff/tree/fix_interface2 |
That looks much simpler indeed. I wonder if we can do an I tried also with your branch here now: diff --git a/sheriff.go b/sheriff.go
index 1dd957a..5b57987 100644
--- a/sheriff.go
+++ b/sheriff.go
@@ -227,10 +227,14 @@ func listContains(a []string, b []string) bool {
// for a possible future alternative to this hack.
// from https://github.com/juju/testing/blob/master/checkers/deepequal.go
func interfaceOf(v reflect.Value) interface{} {
- if !v.IsValid() {
+ if !v.IsValid() || !v.CanInterface() {
return nil
}
- return bypassCanInterface(v).Interface()
+ //return bypassCanInterface(v).Interface()
+ //if !v.CanInterface() {
+ // return nil
+ //}
+ return v.Interface()
}
type flag uintptr
diff --git a/sheriff_test.go b/sheriff_test.go
index 6408b4a..9bed4d1 100644
--- a/sheriff_test.go
+++ b/sheriff_test.go
@@ -474,6 +474,7 @@ type TestInlineStruct struct {
func TestMarshal_InlineStruct(t *testing.T) {
v := TestInlineStruct{
+ tableName: struct{}{},
Field: "World",
Field2: nil,
} This works without panic, which case would be where it should panic, if using CanInterface? |
This does not panic, but skips nil pointer fields, instead of returning "nil" on the output map. Nil pointer fields should output "null" on json, not failing to appear altogether. .IsValid returns false for nil values, this is the problem I was trying to solve too. |
ah, true. Depends on
I see. Ok. Will think a bit about it, thanks! |
diff --git a/sheriff.go b/sheriff.go
index 843101a..d3bb76d 100644
--- a/sheriff.go
+++ b/sheriff.go
@@ -144,6 +144,9 @@ func Marshal(options *Options, data interface{}) (interface{}, error) {
//
// There is support for types implementing the Marshaller interface, arbitrary structs, slices, maps and base types.
func marshalValue(options *Options, v reflect.Value) (interface{}, error) {
+ if !v.IsValid() {
+ return nil, nil
+ }
val := v.Interface()
if marshaller, ok := val.(Marshaller); ok { I added only this change and your added test doesn't panic anymore. Additionally it outputs the following map (when adding a
This looks correct to me, is there something I'm missing still? //Edit: ok I see, if I set the json tag of the unexported field to something else than |
Thanks @RangelReale for bringing that issue up and proposing a fix!
@RangelReale Can you test if #6 works for you? |
Fix #5: skip unexported fields, allow nil values
…m unexported field or method"
reflect.Value.Interface() can panic, so it is needed to call reflect.Value.CanInterface() to prevent this.
I got this error marshalling a struct with this "tableName" field, which is needed by the go-pg package.
type Project struct {
tableName struct{}
sql:"project"
}