unexpected-go

Unexpected Golang behaviors

View on GitHub

Introduction

Let’s say we have a struct type with two fields

type foo struct {
	A int `json:"a"`
	B int `json:"b"`
}

and we try to unmarshal two JSON payloads {"a": 1} and {"b": 1} on it.

The resulting value would be {A: 1, B: 1}: i.e. we can say that json.Unmarshal would merge the existing value with the one coming from the JSON payload.

However, if our type is map[string]foo and we try to unmarshal two JSON payloads:

{
    "key": {"a": 1}
}

and

{
    "key": {"b": 1}
}

Then the result would be just map[key:{A:0 B:1}]: i.e., the second JSON payload would completely overwrite the first one, instead of merging them.

More examples

Struct with struct field

When the outer type is defined as a specific struct instead of a map:

type foo struct {
	A int `json:"a"`
	B int `json:"b"`
}

type bar struct {
	Key foo `json:"key"`
}

Then the result is properly merged, producing the expected {Key:{A:1 B:1}} as the result.

Different map keys

On the other hand, when different map keys are unmarshaled, like {"key1": {"a": 1}} and {"key2": {"b": 1}}, then the outer map is merged too, resulting in map[key1:{A:1 B:0} key2:{A:0 B:1}].

Merging slice values

Finally, when two JSON arrays of one element, [{"a": 1}] and [{"b": 1}] are unmarshaled on the same slice []foo, then the resulting first element of the slice is merged again, producing [{A:1 B:1}].

Why?

The issue golang/go#33487 has a discussion around it, and the main argument is that json package does not perform recursive merging of values, like map values.

This doesn’t seem to apply to slice values, however, which are merged as was shown before.

YAML and friends

This issue can also be reproduced with other format unmarshaling libraries, like gopkg.in/yaml.v3, and it obviously depends on the implementation of each one, but they seem to be largerly consistent on this inconsistency.