-
Notifications
You must be signed in to change notification settings - Fork 18k
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: RawMessage marshals as base64 #14493
Comments
Updated the example to show the proper and improper marshalings. |
Updated again to cover the fact the same misbehavior happens when using it as a map value type, whether the map itself is handed as a pointer or not. |
What change are you suggesting? Documentation? |
When I write custom Marshaler/Unmarshaler types, I tend to use a pointer receiver only for Unmarshal and a value receiver for Marshal, to make this kind of mistake easier for the user to avoid. I think that the usage issue trumps consistency of pointer receivers in that case. But it's too late to change json.RawMessage now. |
The bug should be fixed. The misbehavior is obvious, and undocumented/undesired in every case where it happens. |
What does "fixed" mean? Change RawMessage.MarshalJSON to use a pointer receiver? Special-case RawMessage in the encoder? |
Fixed means making the output match most people's expectation, rather than silently having surprising undesired side-effects that break actual programs at runtime based on unperceived (and unintended) reasoning. |
Has anybody prepared the fix so we know how invasive it'd be? |
Would it actually be an API-breaking change to declare MarshalJSON on the
|
@rogpeppe, that wouldn't work:
|
I went to fix this (see https://golang.org/cl/21811) but it breaks the test for issue #6458 which seems to explicitly lock-in this behavior:
From test: func TestIssue6458(t *testing.T) {
type Foo struct {
M RawMessage
}
x := Foo{RawMessage(`"foo"`)}
b, err := Marshal(&x)
if err != nil {
t.Fatal(err)
}
if want := `{"M":"foo"}`; string(b) != want {
t.Errorf("Marshal(&x) = %#q; want %#q", b, want)
}
b, err = Marshal(x)
if err != nil {
t.Fatal(err)
}
if want := `{"M":"ImZvbyI="}`; string(b) != want {
t.Errorf("Marshal(x) = %#q; want %#q", b, want)
}
} I don't really like that behavior, but I'm not sure whether we can change it. @rsc? |
CL https://golang.org/cl/21811 mentions this issue. |
The value method would cover the pointer type too because that's how Go However as that issue demonstrates, it could indeed change behaviour where What a pity.
|
It would also change the signature of the function from |
The options I see are (1) warnings in the documentation and (2) RawMessage2. |
Could we not make this something that vet handles? Passing anything containing a non-addressable RawMessage (directly or transitively) into any of the json decoding functions is clearly a mistake (since if they want that behavior, they can just use a normal |
dominikh: Yes, you're right, I hadn't thought about method expressions. extemporalgenome: I don't think so. Vet often can't see the In my particular case, I wanted to round-trip map[string]json.RawMessage; To be honest, the implementation of json.RawMessage is so trivial |
I think this is an instance of "just lock down the current behavior and move on". The fix is to use *RawMessage, right? |
I just noticed that this is actually a duplicate of #6528. Though maybe that one should be closed in favor of this one, which has a lot more discussion. |
For me, the fix is defining my own RawMessage and have a custom linter to ban the use of json.RawMessage. Not too bad. |
I just spent few hours chasing this issue. Admittedly I didn't look in the issue database, but my guess is that others are likely not to do it as well. May I suggest at least a warning in the RawMessage documentation? |
Despite my comment from 2013 on #6528 and my comment here from April 2016, today I am having a very hard time stomaching the current behavior. It's hard to see this as anything other than a bug we should fix. The whole reason RawMessage exists is to marshal its raw content. If it's not doing that, it seems like we should fix it. Yes, it might break people expecting something else, but those people can use plain []byte. The difference in Gustavo's program https://play.golang.org/p/bHuvfyb7qB between Marshal(color1) and Marshal(&color1) further convinces me that we should fix RawMessage. It already "does the right thing" when it appears in an addressable context. I think we can overrule the test case added in #6458, at least tentatively, and see whether any justifiable real code breaks. Let's fix this (change RawMessage.Marshal to be a value method) and see if we can make it stick. Leaving for @quentinmit. |
I already had a CL for this (above). I can just resurrect it. |
CL https://golang.org/cl/32472 mentions this issue. |
This CL expands upon a change made in (http://golang.org/cl/21811) to ensure that a nil RawMessage gets serialized as "null" instead of being a nil slice. The added check only triggers when the RawMessage is nil. We do not handle the case when the RawMessage is non-nil, but empty. Fixes #17704 Updates #14493 Change-Id: I0fbebcdd81f7466c5b78c94953afc897f162ceb4 Reviewed-on: https://go-review.googlesource.com/32472 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This test fails on go 1.8+, since the behavior changed: golang/go#14493 (comment) The test also doesn't test ffjson's code.
The encoding/json package marshals RawMessage properly or incorrectly as base64 based on whether its container is marshaled by value or as a pointer, which is extremely subtle and trivial to get wrong silently.
It also consistently fails to marshal the RawMessage value properly when used as a map value type.
Sample code demonstrating these problems:
https://play.golang.org/p/bHuvfyb7qB
The text was updated successfully, but these errors were encountered: