-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
encoding/json: cannot unmarshal custom interface value #45512
Comments
This comment has been minimized.
This comment has been minimized.
Here's a tweaked program: https://play.golang.org/p/w6_c2eoUF7B Note that, in the "it works" scenario, you end up decoding into That fails in the case where the interface has one method, because a basic type cannot satisfy a non-empty interface, hence the error. What were you expecting should happen, in terms of the final types? |
Also, if you were expecting to decode into the inner So I think that this is working as intended, as far as I can tell. If you find anything confusing, perhaps we need better docs or error messages. |
I consider the inability to unmarshal into an unaddressable value (which can occur with maps and interfaces) a flaw in the implementation. Unfortunately, it's hard to change this behavior as people have likely come to depend on the current behavior. 😞 |
So, to give a little more context, because I tried to boil down the example to just what was failing, but I messed that up: I am trying to deserialise an externally tagged enum: type Exported struct{
Shared string
Enum Enum
}
type Enum interface{
private()
}
type One struct{ /*...*/ }
func (One) private() {}
type Two struct{ /*...*/ }
func (Two) private() {}
type Three struct{ /*...*/ }
func (Three) private() {} And I wanted to avoid having to do this: func (e *Exported) UnmarshalJSON(b []byte) error {
var wire struct{
Shared string
Type string
Enum json.RawMessage
}
json.Unmarshal(b, &wire)
switch wire.Type {
case "one":
var v One
json.Unmarshal(wire.Enum, &v)
e.Enum = v
case "two":
var v Two
json.Unmarshal(wire.Enum, &v)
e.Enum = v
case "three":
var v Three
json.Unmarshal(wire.Enum, &v)
e.Enum = v
}
return nil
} Since all three cases do exactly the same thing, I wanted to do this: switch wire.Type {
case "one":
e.Enum = One{}
case "two":
e.Enum = Two{}
case "three":
e.Enum = Two{}
}
json.Unmarshal(wire.Enum, &e.Enum) And it seemed intuitive that I should be able to address the concrete type inside |
I think your best there is to use pointers. Interfaces always store pointers anyway, so it's not like storing a |
Is there a reason why I should not be allowed to use a value here? The limitation seems arbitrary. I am also not sure what your perf argument is, but I would expect two pointers would be worse than one if that held water. Instead, I strongly prefer values in cases like this to enforce imutability. |
We likely can't change that behavior without breaking existing users, like @dsnet said earlier.
Not sure what you mean. In your
I tend to agree, but you're limited by what is possible between interfaces and the well established encoding/json behavior, I'm afraid. |
Can you clarify or point to what would be a breaking change? I do not follow how adding the ability to do something that previously did not work is an issue.
Is this documented anywhere? Calling this "well established" certainly caught me off guard. |
If you haven't heard of it before, you might want to give Hyrum's Law a read. The |
Based on my reading of the issue, it doesn't look like this is something we can fix without the potential for widespread breakage. I'm inclined to close the issue, unless we can show that not many people depend on the behavior that might want to be changed. |
@mknyszek can't it be fixed and be put behind a knob in the decoder? |
There's quite a bit a functionality that we would like to change with The best course of action is to systemically look at the entirety of the |
@dsnet wouldn't that actually work fine though? adding a v2 package. |
I'm not sure I understand the question. Of course a v2 package could fix this issue without worrying about backwards compatibility. That said, a v2 package is beyond the scope of this issue. |
I will say with generics so close, it may be better to try and poc an optimised implementation that does not rely upon runtime reflection, as it is so slow. |
Generics may provide type safety in some situations, but is not a magic feature that's going to speed up JSON serialization. In order to make serialization of arbitrary Go data structures faster, you would need to be able to change the serialization logic at compile-time based on arbitrary Go types†. Generics does not provide that. The main ways to speed up JSON serialization‡ is either through the use of † The generics proposal does allow type switching over specific types to provide specialized implementations. However, that doesn't help JSON since the set of possible types to switch over is not finite. |
In that case, I would request we start a v2 tracking issue to document what we would like to change and confirm when we hit a critical mass. (But I agree that is out of scope for this issue.) |
@colin-sitehost that's been happening since last year, see https://twitter.com/mvdan_/status/1280133100673159171. |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
https://play.golang.org/p/VMWbHeo54sj
What did you expect to see?
Any interface should be able to be assignable:
What did you see instead?
Instead it appears that
interface{}
is special, and unlessmain.private
implementsjson.Unmarshaler
it willerror
at runtime:This restriction does not seem meaningful, and I could not find it documented in source why this was enforced. Can we not mutate the pointed value inside a provided interface regardless of type?
The text was updated successfully, but these errors were encountered: