-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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: unmarshal into slice reuses element data between len and cap #21092
Comments
This is working as intended. See https://blog.golang.org/slices for how slices work. "resets the slice length to zero and then appends each element to the slice" and "resets the slice length to zero and then modifies the elements of the underlying slice" are the same thing. |
https://golang.org/pkg/encoding/json/#Unmarshal
@bradfitz This may be working as intended, but not as described in the docs. The docs describe the slice length being set to zero (which happens), then appending (which is not what is happening). The builtin append will replace the struct in a slice wholly. Unmarshal does not. Since the builtin append does one thing, and Unmarshal does a second, we should not say that Unmarshal is appending. |
TL;DR Extended version:
On the encoding/json Unmarshal documentation this is mentioned:
Now, we could argue that in the use case that is described in this issue, a json object with just In the counterexample of the manual An option is to set the element value to the target type "zero value" before updating it by assigning the decoded value from the json: in such a way everything would be assigned to its zero value (as the if i < v.Len() {
// Calculate zero value for target type
z := reflect.Zero(v.Type().Elem())
// Assign the zero value to the element
v.Index(i).Set(z)
// Decode into element.
d.value(v.Index(i))
} In general, I miss the use case of passing an initialised slice to unmarshal new values that override the old values, where I could just pass a new empty slice (and I agree that the description in this case is misleading). I have a green test, but I'm not sure of the performance implications of adding those steps (i.e. setting the value twice, zero and then decoded one). At a first sight, I would probably suggest to update the documentation as I expect someone is relying on that behaviour. [update] |
This does not look like working as intended and more like data corruption. Appending must overwrite the old value. This must print 1/1, but it prints 1/3: |
The code does not seem to regard even slice len. So if one uses this to get some kind of merging, she can get unexpected garbage with whatever was there between len and cap. |
json decoding behavior is somewhat surprising (see // golang/go#21092). This behavior is especially easy to hit in tests that reuse reply objects. To avoid any surprises, we zero the reply.
I agree that when appending to a slice, unmarshal should start with fresh zeroed slice elements and not the old slice elements that happen to sit between len and cap. Too late for Go 1.10 though. Dmitry's example is clearly incorrect behavior and should be fixed. However, the fix should preserve the current behavior in this variant: https://play.golang.org/p/H0kRWEEiEW. (Resetting the length to 0 does not mean resetting the capacity to 0.) |
Duplicate (closed) bug with other repros: #24155 |
Change https://golang.org/cl/98516 mentions this issue: |
As someone who actually depends on the fact that Unmarshal does NOT zero out maps/slices, this would break a lot of my code. |
@OneOfOne out of curiosity, what is your usecase that requires data to be retained for maps/slices? |
@JeremyLoy in one case to override the default values in a map and in a few other, filling the map from multiple input files. |
Maybe you can add an extra field to the json decoder to make this optional. |
@OneOfOne Based on reading @rsc's comment, I don't think your case will be affected. Please excuse my rambling explanation: Unmarshalling onto existing slices would continue to merge data for elements up to If you are for some reason reading in the first file, then setting your slice header back |
This change has produced a small but significant number of test failures in Google's codebase. A reduced example of one case which was broken is: type T struct {
Index int
Elements []json.RawMessage
}
var message T
tmp := []interface{}{&message.Index, &message.Elements}
err = json.Unmarshal(raw[0], &tmp)
if err != nil {
return message, err
}
return message, nil |
The change is being reverted. Reopening to allow me to retry a smaller change in 1.16. |
I didn't have time to try this again in 1.16; moving to 1.17. |
... and here I am, looking for a way to actually append new elements at the end of existing slice. package main
import (
"encoding/json"
"fmt"
)
func unmarshal(v any) error {
data1 := []byte(`[{"Name":"Tom"},{"Name":"Michael"}]`)
if err := json.Unmarshal(data1, v); err != nil {
return err
}
data2 := []byte(`[{"Name":"Alice"},{"Name":"Frank"}]`)
if err := json.Unmarshal(data2, v); err != nil {
return err
}
return nil
}
type Person struct {
Name string
}
func main() {
var people []Person
if err := unmarshal(&people); err != nil {
panic(err)
}
fmt.Println(people)
} Would be nice to have a way to get |
What should it do with integer fields? Sum? Or calculate an average? ;) |
when some subfields of authInternalUsers and authHTTPExclude were not set explicitly in the configuration file, default values were used in their place. This is caused by a strange behavior of Go (golang/go#21092)
when some subfields of authInternalUsers or authHTTPExclude were not set explicitly in the configuration file, default values were used in their place. This is caused by a strange behavior of Go (golang/go#21092)
when some subfields of authInternalUsers or authHTTPExclude were not set explicitly in the configuration file, default values were used in their place. This is caused by a strange behavior of Go (golang/go#21092)
when some subfields of authInternalUsers or authHTTPExclude were not set explicitly in the configuration file, default values were used in their place. This is caused by a strange behavior of Go (golang/go#21092)
… than unmarshalling into the input encoding/json behaves surprisingly ([#27172](golang/go#27172), [#31924](golang/go#31924), [#26946](golang/go#26946), [#21092](golang/go#21092)) when unmarshalling into non-empty structs, and this behaviour is unexpected in this library anyway; no other methods do any user-facing unmarshalling or pointer mutation. This commit changes all of the `Update` methods that unmarshall into their inputs to no longer do that, and return a newly-allocated struct instead.
… than unmarshalling into the input encoding/json behaves surprisingly ([#27172](golang/go#27172), [#31924](golang/go#31924), [#26946](golang/go#26946), [#21092](golang/go#21092)) when unmarshalling into non-empty structs, and this behaviour is unexpected in this library anyway; no other methods do any user-facing unmarshalling or pointer mutation. This commit changes all of the `Update` methods that unmarshall into their inputs to no longer do that, and return a newly-allocated struct instead.
… than unmarshalling into the input encoding/json behaves surprisingly ([#27172](golang/go#27172), [#31924](golang/go#31924), [#26946](golang/go#26946), [#21092](golang/go#21092)) when unmarshalling into non-empty structs, and this behaviour is unexpected in this library anyway; no other methods do any user-facing unmarshalling or pointer mutation. This commit changes all of the `Update` methods that unmarshall into their inputs to no longer do that, and return a newly-allocated struct instead.
… than unmarshalling into the input encoding/json behaves surprisingly ([#27172](golang/go#27172), [#31924](golang/go#31924), [#26946](golang/go#26946), [#21092](golang/go#21092)) when unmarshalling into non-empty structs, and this behaviour is unexpected in this library anyway; no other methods do any user-facing unmarshalling or pointer mutation. This commit changes all of the `Update` methods that unmarshall into their inputs to no longer do that, and return a newly-allocated struct instead.
… than unmarshalling into the input encoding/json behaves surprisingly ([#27172](golang/go#27172), [#31924](golang/go#31924), [#26946](golang/go#26946), [#21092](golang/go#21092)) when unmarshalling into non-empty structs, and this behaviour is unexpected in this library anyway; no other methods do any user-facing unmarshalling or pointer mutation. This commit changes all of the `Update` methods that unmarshall into their inputs to no longer do that, and return a newly-allocated struct instead.
… than unmarshalling into the input encoding/json behaves surprisingly ([#27172](golang/go#27172), [#31924](golang/go#31924), [#26946](golang/go#26946), [#21092](golang/go#21092)) when unmarshalling into non-empty structs, and this behaviour is unexpected in this library anyway; no other methods do any user-facing unmarshalling or pointer mutation. This commit changes all of the `Update` methods that unmarshall into their inputs to no longer do that, and return a newly-allocated struct instead.
… than unmarshalling into the input encoding/json behaves surprisingly ([#27172](golang/go#27172), [#31924](golang/go#31924), [#26946](golang/go#26946), [#21092](golang/go#21092)) when unmarshalling into non-empty structs, and this behaviour is unexpected in this library anyway; no other methods do any user-facing unmarshalling or pointer mutation. This commit changes all of the `Update` methods that unmarshall into their inputs to no longer do that, and return a newly-allocated struct instead.
What version of Go are you using (
go version
)?go1.8
What operating system and processor architecture are you using (
go env
)?GOOS=nacl
GOARCH=amd64p32
What did you do?
https://play.golang.org/p/lbYUhgOe--
What did you expect to see?
According to the json Unmarshal docs, Unmarshal resets the slice length to zero and then appends each element to the slice.
What did you see instead?
Instead Unmarshal resets the slice length to zero and then modifies the elements of the underlying slice.
The text was updated successfully, but these errors were encountered: