-
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 map[string]interface{} target mishandled when passed as an interface{} #33487
Comments
This is similar to, if not a duplicate of #26946, where we considered adding a special case to Unmarshal that looks inside an interface{} for a struct or a map to reuse. However, it was thought that this change would make Unmarshal inconsistent, and it would be better to document this behavior. So I would modify your first example to take the address of the map: var map1 interface{}
m := map[string]interface{}{
"key1": "data1",
}
map1 = &m
json.Unmarshal([]byte(`{"key2":"data2"}`), map1)
fmt.Println("map1:", map1) // map1: &map[key1:data1 key2:data2]
fmt.Println("m:", m) // m: map[key1:data1 key2:data2] |
After reading the linked issue I believe I better understand the decisions that were made, however, I still think there is a strong argument for providing the merging behavior for map[string]interface{} as a special case (and different to the behaviour when unmarshaling into structs).
|
I think the key here is to not predeclare an interface{} value that you pass into Unmarshal. You get all the properties you ask for if you simply pass in a *map[string]interface{}. m := map[string]interface{}{
"key1": "data1",
}
json.Unmarshal([]byte(`{"key2":"data2"}`), &m)
fmt.Println("m:", m) // m: map[key1:data1 key2:data2] This allows the reflection in Unmarshal to work the way you desire. |
No, this just copes with the top level map - if you look at map3 in the initial example you will see that I want merging to happen all the way down the hierarchy (and I think the docs imply it should work like this). Including using an example like the following: var map3 map[string]interface{}
json.Unmarshal([]byte(`{"key1":{"key1.1":"data2"}}`), &map3)
json.Unmarshal([]byte(`{"key1":{"key1.2":"data2"}}`), &map3)
fmt.Println("map3:", map3) This should print: |
I see, sorry I misunderstood your question. Unmarshal does not perform recursive merging, so you'd have to manually add elements from one map to the other, or serialization/reflection-based helper functions to do it for you. https://play.golang.org/p/Iu4P8qgdldb This is what libraries like https://github.com/peterbourgon/mergemap do, as mentioned here: https://groups.google.com/forum/#!topic/golang-nuts/nLCy75zMlS8. EDIT: A more detailed answer for why can be found in this issue: #21857. |
Thanks for the issue link, that is another similar request. I see, from the comments on that issue, that this is unlikely to progress and that it could only go forward as new functionality in the json package to preserve backwards compatibility. For now, using something like the mergemap you link to is the best option. I suppose the key point remaining is that the current json docs indicate that the merge should happen(from https://golang.org/pkg/encoding/json/#Unmarshal):
When I read this, I expected the rules to be applied recursively, which led to this issue. |
It may be worthwhile to document that keeping existing entries does not imply the recursive merging of maps. @gopherbot, add Documentation, remove NeedsInvestigation |
Let's consolidate documentation needs around this in the aforementioned issue. |
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?
Used a reference to the same map in multiple call to json.Unmarshal. See https://play.golang.org/p/bum-Q01e96M
What did you expect to see?
Expected JSON unmarshaling into maps to merge entries between different calls to json.Unmarshal, as indicated by the docs.
The play example should output:
What did you see instead?
Whenever the type of the unmarshal target is interface{} then a replacement value is created even if the existing entry is of type map[string]interface{} and is a suitable instance for unmarshal to use.
The play example outputs:
The text was updated successfully, but these errors were encountered: