I can't understand a strange behavior of custom marshal int
to string
.
Here is an example:
package main
import (
"encoding/json"
"fmt"
)
type Int int
func (a Int) MarshalJSON() ([]byte, error) {
test := a / 10
return json.Marshal(fmt.Sprintf("%d-%d", a, test))
}
func main() {
array := []Int{100, 200}
arrayJson, _ := json.Marshal(array)
fmt.Println("array", string(arrayJson))
maps := map[Int]bool{
100: true,
200: true,
}
mapsJson, _ := json.Marshal(maps)
fmt.Println("map wtf?", string(mapsJson))
fmt.Println("map must be:", `{"100-10":true, "200-20":true}`)
}
The output is:
array ["100-10","200-20"]
map wtf? {"100":true,"200":true}
map must be: {"100-10":true, "200-20":true}
https://play.golang.org/p/iiUyL2Hc5h_P
What am I missing?
This is the expected outcome, which is documented at json.Marshal()
:
Map values encode as JSON objects. The map's key type must either be a string, an integer type, or implement encoding.TextMarshaler. The map keys are sorted and used as JSON object keys by applying the following rules, subject to the UTF-8 coercion described for string values above:
- string keys are used directly - encoding.TextMarshalers are marshaled - integer keys are converted to strings
Note that map keys are handled differently than values of properties because map keys in JSON are the property names which are always string
values (while property values may be JSON text, number and boolean values).
As per the doc, if you want it to work for map keys as well, implement encoding.TextMarshaler
:
func (a Int) MarshalText() (text []byte, err error) {
test := a / 10
return []byte(fmt.Sprintf("%d-%d", a, test)), nil
}
(Note that MarshalText()
is ought to return "just" simple text, not JSON text, hence we omit JSON marshaling in it!)
With this, output will be (try it on the Go Playground):
array ["100-10","200-20"] <nil>
map wtf? {"100-10":true,"200-20":true} <nil>
map must be: {"100-10":true, "200-20":true}
Note that encoding.TextMarshaler
is enough as that is also checked when marsaling as values, not just for map keys. So you don't have to implement both encoding.TextMarshaler
and json.Marshaler
.
If you do implement both, you can have different output when the value is marshaled as a "simple" value and as a map key because json.Marshaler
takes precedence when generating a value:
func (a Int) MarshalJSON() ([]byte, error) {
test := a / 100
return json.Marshal(fmt.Sprintf("%d-%d", a, test))
}
func (a Int) MarshalText() (text []byte, err error) {
test := a / 10
return []byte(fmt.Sprintf("%d-%d", a, test)), nil
}
This time the output will be (try it on the Go Playground):
array ["100-1","200-2"] <nil>
map wtf? {"100-10":true,"200-20":true} <nil>
map must be: {"100-10":true, "200-20":true}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With