How to custom marshal map keys in JSON - json

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}

The accepted answer is great but I've had to re-search for this enough times that I wanted to put a complete answer regarding marshal/unmarshal with examples so next time I can just copy paste as a starting point :)
The things I often search for include:
encode custom type to sql database
json encode enum int as string
json encode map key but not value
In this example I make a custom Weekday type which matches time.Weekday int values but allows for the string value in request/response json and in the database
This same thing can be done with any int enum using iota to have the human readable value in json and database
Playground example with tests: https://go.dev/play/p/aUxxIJ6tY9K
The important bit is here though:
var (
// read/write from/to json values
_ json.Marshaler = (*Weekday)(nil)
_ json.Unmarshaler = (*Weekday)(nil)
// read/write from/to json keys
_ encoding.TextMarshaler = (*Weekday)(nil)
_ encoding.TextUnmarshaler = (*Weekday)(nil)
// read/write from/to sql
_ sql.Scanner = (*Weekday)(nil)
_ driver.Valuer = (*Weekday)(nil)
)
// MarshalJSON marshals the enum as a quoted json string
func (w Weekday) MarshalJSON() ([]byte, error) {
return []byte(`"` + w.String() + `"`), nil
}
func (w Weekday) MarshalText() (text []byte, err error) {
return []byte(w.String()), nil
}
func (w *Weekday) UnmarshalJSON(b []byte) error {
return w.UnmarshalText(b)
}
func (w *Weekday) UnmarshalText(b []byte) error {
var dayName string
if err := json.Unmarshal(b, &dayName); err != nil {
return err
}
d, err := ParseWeekday(dayName)
if err != nil {
return err
}
*w = d
return nil
}
// Value is used for sql exec to persist this type as a string
func (w Weekday) Value() (driver.Value, error) {
return w.String(), nil
}
// Scan implements sql.Scanner so that Scan will be scanned correctly from storage
func (w *Weekday) Scan(src interface{}) error {
switch t := src.(type) {
case int:
*w = Weekday(t)
case int64:
*w = Weekday(int(t))
case string:
d, err := ParseWeekday(t)
if err != nil {
return err
}
*w = d
case []byte:
d, err := ParseWeekday(string(t))
if err != nil {
return err
}
*w = d
default:
return errors.New("Weekday.Scan requires a string or byte array")
}
return nil
}
Note that the var block simply forces you to implement the methods correctly else it won't compile.
Also note that if you exclude MarshalJSON then go will use MarshalText if it is there, so if you want only the key to have a custom marshal but to have the default behavior for the value then you should not use these methods on your main type, but instead have a wrapper type that you only use for map keys
type MyType struct{}
type MyTypeKey MyType
var (
// read/write from/to json keys
_ encoding.TextMarshaler = (*MyTypeKey)(nil)
_ encoding.TextUnmarshaler = (*MyTypeKey)(nil)
)
func (w MyTypeKey) MarshalText() (text []byte, err error) {
return []byte(w.String()), nil
}
func (w *MyTypeKey) UnmarshalText(b []byte) error {
*w = MyTypeKey(ParseMyType(string(b)))
return nil
}
Feel free to improve this answer, I hope others find it helpful and I hope I can find it again next time I want this and search for it again myself :)

Related

How can I decode to an embedded struct?

I wish to be able to use .Decode() on a response body to populate a struct without first having to attempt to figure out which type of struct I should decode to.
I have a generic struct Match to hold information about a game that was played e.g. a match in Fortnite. Within this struct, I use MatchData to hold the entirety of the game's match data.
When decoding into the MatchData struct, I'm finding the underlying embedded type is initialised, but with all default values, and not the values from the respose.
type Match struct {
MatchID int `json:"match_id"`
GameType int `json:"game_type"`
MatchData *MatchData `json:"match_data"`
}
type MatchData struct {
MatchGame1
MatchGame2
}
type MatchGame1 struct {
X int `json:"x"`
Y int `json:"y"`
}
type MatchGame2 struct {
X int `json:"x"`
Y int `json:"y"`
}
func populateData(m *Match) (Match, error) {
response, err := http.Get("game1.com/path")
if err != nil {
return nil, err
}
// Here, m.MatchData is set with X and Y equal to 0
// when response contains values > 0
err = json.NewDecoder(response.Body).Decode(&m.MatchData)
if err != nil {
return nil, err
}
return m, nil
}
Edit
Example expected JSON payload.
{
"x": 10,
"y": 20
}
I can solve the issue by checking m.GameType, creating a struct that corresponds and then assigning it to m.MatchData, but if I wanted to add another 100 game APIs, I'd prefer if the function could be agnostic of it.
I'm not sure if this is even possible, but thanks in advance.
The approach in the question will not work because the embedded structs share field names. Try this approach.
Declare a map that associates game type identifiers with the associated Go types. This is only code related to decoding that knows about the hundreds of game types.
var gameTypes = map[int]reflect.Type{
1: reflect.TypeOf(&MatchGame1{}),
2: reflect.TypeOf(&MatchGame2{}),
}
Decode the match data to a raw message. Use the game type to create a match data value and decode to that value.
func decodeMatch(r io.Reader) (*Match, error) {
// Start with match data set to a raw messae.
var raw json.RawMessage
m := &Match{MatchData: &raw}
err := json.NewDecoder(r).Decode(m)
if err != nil {
return nil, err
}
m.MatchData = nil
// We are done if there's no raw message.
if len(raw) == 0 {
return m, nil
}
// Create the required match data value.
t := gameTypes[m.GameType]
if t == nil {
return nil, errors.New("unknown game type")
}
m.MatchData = reflect.New(t.Elem()).Interface()
// Decode the raw message to the match data.
return m, json.Unmarshal(raw, m.MatchData)
}
Run it on the playground.

How to set a default value when unmarshal a json to struct [duplicate]

This question already has answers here:
How to specify default values when parsing JSON in Go
(4 answers)
Closed 3 years ago.
I want to set a default value to a field when unmarshal from a json string.
I know i can set the value i want before unmarshaling, I think it's not a beautiful way.
Is there any way else, like using a "default" tag?
func main() {
in := "{}"
myStruct := StructTest{}
json.Unmarshal([]byte(in), &myStruct)
fmt.Println(myStruct)
}
type StructTest struct {
V int64 `default:1`
}
What you can do is define a custom unmarshal function and decide there if you want to use the default value or not. In case you have other fields in StructTest you will want to create an alias for StructTest in UnmarshalJSON so that other fields will still be treated the same, while V will be overridden.
The snippet below shows one way to do it, also check out this working Go playground example.
type StructTest struct {
V int64
Other string // this field should be unmarshaled the regular way
}
func (st *StructTest) UnmarshalJSON(data []byte) error {
// create alias to prevent endless loop
type Alias StructTest
tmp := struct {
*Alias
V *int64
}{
Alias: (*Alias)(st),
}
// unmarshal into temporary struct
err := json.Unmarshal(data, &tmp)
if err != nil {
return err
}
// check if V was supplied in JSON and set default value if it wasn't
if tmp.V == nil {
st.V = 1 // default
} else {
st.V = *tmp.V
}
return nil
}
EDIT:
Actually for this simple example it can be done even simpler:
func (st *StructTest) UnmarshalJSON(data []byte) error {
st.V = 1 // set default value before unmarshaling
type Alias StructTest // create alias to prevent endless loop
tmp := (*Alias)(st)
return json.Unmarshal(data, tmp)
}
The short answer is no, and for cases like this:
type T struct {
Field1 int
Field2 int
}
...
func foo(data []byte) error {
var x T
if err := json.Unmarshal(data, &x); err != nil {
return err
}
// now set Field1 and/or Field2 to default values if needed
// ... but this is hard to do ...
use(x)
return nil
}
it's simple and easy to do it the other way, i.e.:
func foo(data []byte) error {
x := T{Field1: 99 /* red ballons */, Field2: 42 /* The Answer */ }
if err := json.Unmarshal(data, &x); err != nil {
return err
}
// now Field1 and/or Field2 are already set to default values if needed
use(x)
return nil
}
But what if the default is hard to compute? For instance:
type T2 struct {
Answer int
Question string
}
and function foo should have a default Answer of 42 as before, but the default Question should be the one that the mice were trying to compute, and obviously we don't want to spend a few millenia computing it if we don't have to. So we can't pre-initialize x, and we need to know if a question was provided.1
Yet another alternative, of course, is to decode into a struct with a pointer, then convert that nullable thing to the struct that doesn't have a pointer; we know whether the nullable variant's field was filled in or not because it's non-nil if it was filled-in. This produces code of this sort:
type T_ struct {
Answer int
Question *string
}
and fill in one variable x_ of type T_:
func foo(data []byte) error {
var x T2
x_ := T_{Answer: 42}
if err := json.Unmarshal(data, &x_); err != nil {
return err
}
x.Answer = x_.Answer
if x_.Question = nil {
x.Question = computeTheQuestion(x_.Answer)
} else {
x.Question = *x_.Question
}
use(x)
return nil
}
However, once again I lament (at least slightly) the ability to unmarshal json data with code like this hypothetical interface:
func foo(data []byte) error {
var objectish map[string]interface{}
if err := json.Unmarshal(data, &objectish); err != nil {
return err
}
x := T2{Answer: 42}
if err := json.ReUnmarshal(objectish, &x); err != nil {
return err
}
// We now know that the object-ish decoded JSON has the right
// "shape" for a T, and variable x is filled in with a default
// Answer. Its Question is the empty string if there was an
// empty Question, or if there was *no* Question at all, so
// now let's find out if we need to compute the right Question.
if _, ok := objectish["Question"]; !ok {
x.Question = computeTheQuestion(x.Answer)
}
use(x) // pass x to a hoopy frood
return nil
}
This hypothetical ReUnmarshal—which could actually just be Unmarshal itself, really—would, if given an interface{}, treat its value as resulting from an earlier Unmarshal and just re-type the result. If given a map[string]interface{} it would re-type the object. If given a map[string]map[string]interface{} it would do the obvious thing here as well, and so on. The only change the user sees is that Unmarshal (or ReUnmarshal, if changing the type signature is too rude) now takes:
[]byte, or
string, probably, just for convenience, or
json.RawMessage, because that already does almost the right thing, or
map[string]T for T being any of the types it accepts (including map recursively)
and it does the obvious thing with each of these.
Note that this is pretty similar to using json.RawMessage. However, when using json.RawMessage, we're right back to writing out a variant of the original struct type, with the same field names. See this example in the Go Playground, where we have to declare x_ (rather than objectish) with the type that uses the json.RawMessage argument.
(Alternatively, you can create a type with its own unmarshal function, as in lmazgon's answer. But again you must invent a type, rather than just decoding right into the already-supplied target type.)
1In this case, we could use Question *string, or assume that null strings are not allowed, or something, but this example is pretty reductionist. Suppose instead that we're supposed to compute a bignum with an accurate pi to some number of places, or any other hard but realistic computation. The point here is that there can be situations where pre-loading the defaults is relatively expensive, so we'd like to avoid that.

Golang: JSON: How do I unmarshal array of strings into []int64

Golang encoding/json package lets you use ,string struct tag in order to marshal/unmarshal string values (like "309230") into int64 field. Example:
Int64String int64 `json:",string"`
However, this doesn't work for slices, ie. []int64:
Int64Slice []int64 `json:",string"` // Doesn't work.
Is there any way to marshal/unmarshal JSON string arrays into []int64 field?
Quote from https://golang.org/pkg/encoding/json:
The "string" option signals that a field is stored as JSON inside a JSON-encoded string. It applies only to fields of string, floating point, integer, or boolean types. This extra level of encoding is sometimes used when communicating with JavaScript programs:
For anyone interested, I found a solution using a custom type having MarshalJSON() and UnmarshalJSON() methods defined.
type Int64StringSlice []int64
func (slice Int64StringSlice) MarshalJSON() ([]byte, error) {
values := make([]string, len(slice))
for i, value := range []int64(slice) {
values[i] = fmt.Sprintf(`"%v"`, value)
}
return []byte(fmt.Sprintf("[%v]", strings.Join(values, ","))), nil
}
func (slice *Int64StringSlice) UnmarshalJSON(b []byte) error {
// Try array of strings first.
var values []string
err := json.Unmarshal(b, &values)
if err != nil {
// Fall back to array of integers:
var values []int64
if err := json.Unmarshal(b, &values); err != nil {
return err
}
*slice = values
return nil
}
*slice = make([]int64, len(values))
for i, value := range values {
value, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
(*slice)[i] = value
}
return nil
}
The above solution marshals []int64 into JSON string array. Unmarshaling works from both JSON string and integer arrays, ie.:
{"bars": ["1729382256910270462", "309286902808622", "23"]}
{"bars": [1729382256910270462, 309286902808622, 23]}
See example at https://play.golang.org/p/BOqUBGR3DXm
As you quoted from json.Marshal(), the ,string option only applies to specific types, namely:
The "string" option signals that a field is stored as JSON inside a JSON-encoded string. It applies only to fields of string, floating point, integer, or boolean types.
You want it to work with a slice, but that is not supported by the json package.
If you still want this functionality, you have to write your custom marshaling / unmarshaling logic.
What you presented works, but it is unnecessarily complex. This is because you created your custom logic on slices, but you only want this functionality on individual elements of the slices (arrays). You don't want to change how an array / slice (as a sequence of elements) is rendered or parsed.
So a much simpler solution is to only create a custom "number" type producing this behavior, and elements of slices of this custom type will behave the same.
Our custom number type and the marshaling / unmarshaling logic:
type Int64Str int64
func (i Int64Str) MarshalJSON() ([]byte, error) {
return json.Marshal(strconv.FormatInt(int64(i), 10))
}
func (i *Int64Str) UnmarshalJSON(b []byte) error {
// Try string first
var s string
if err := json.Unmarshal(b, &s); err == nil {
value, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return err
}
*i = Int64Str(value)
return nil
}
// Fallback to number
return json.Unmarshal(b, (*int64)(i))
}
And that's all!
The type using it:
type Foo struct {
Bars []Int64Str `json:"bars"`
}
Testing it the same way as you did yields the same result. Try it on the Go Playground.

How to parse both plain and enquoted JSON numbers in Go?

I'm dealing with a third-party JSON-based API. Usually it wraps all numbers in quotes, but sometimes it doesn't. Nothing I can do about it.
I'm trying to come up with a solution, which parses the numbers regardless of whether they're enquoted or not. Standard library provides a ,string field tag, which allows to map numeric fields to enquoted values, but, unfortunately, it then fails to process the value, if it's not in quotes.
func test(s string) {
err := json.Unmarshal([]byte(s), &struct {
F1 float64
F2 float64 `json:",string"`
}{})
if err != nil {
fmt.Println(err)
return
}
fmt.Println("success")
}
func main() {
test(`{"f1": 1.23}`) // success
test(`{"f1": "1.23"}`) // cannot unmarshal string into Go value of type float64
test(`{"f2": 1.23}`) // invalid use of ,string struct tag, trying to unmarshal unquoted value into ...
test(`{"f2": "1.23"}`) // success
}
The Go Playground
Is there a way around this?
The proper solution is to "clone" float64 and define custom MarshalJSON and UnmarshalJSON for it:
type jsonFloat64 float64
func (f jsonFloat64) MarshalJSON() ([]byte, error) {
return json.Marshal(float64(f))
}
func (f *jsonFloat64) UnmarshalJSON(data []byte) error {
if len(data) >= 2 && data[0] == '"' && data[len(data)-1] == '"' {
data = data[1 : len(data)-1]
}
var tmp float64
err := json.Unmarshal(data, &tmp)
if err != nil {
return err
}
*f = jsonFloat64(tmp)
return nil
}
Then you'd be able to do something like this:
func test(s string) {
err := json.Unmarshal([]byte(s), &struct {
F jsonFloat64
}{})
if err != nil {
fmt.Println(err)
return
}
fmt.Println("success")
}
func main() {
test(`{"f": 1.23}`) // success
test(`{"f": "1.23"}`) // success
}
The Go Playground
Feel free to adjust UnmarshalJSON to your needs, mine is pretty strict about the spacing. Credit goes to Dave C, this was heavily inspired by his comment on another question (which also features more variations on the solution above).
Alternatively, you could pre-process the JSON data with a regular expression, but don't do this if the solution above is a viable option, it's much faster.
re := regexp.MustCompile(`(":\s*)([\d\.]+)(\s*[,}])`)
rawJsonByteArray = re.ReplaceAll(rawJsonByteArray, []byte(`$1"$2"$3`))

Ignore JSON tags when marshalling

I am getting JSON data from an external source. The field names in this JSON are not something I want to carry with me, so I am converting them to names that make sense to me using the json:"originalname" tags.
When I marshal such an object back to JSON, I naturally get the ugly (original) names again.
Is there a way to ignore tags when marshalling? Or a way to specify a different name for marshall and unmarshall?
To clarify, I have prepared an example in the playground and pasted the same code below.
Thanks in advance.
package main
import (
"encoding/json"
"fmt"
)
type Band struct {
Name string `json:"bandname"`
Albums int `json:"albumcount"`
}
func main() {
// JSON -> Object
data := []byte(`{"bandname": "AC/DC","albumcount": 10}`)
band := &Band{}
json.Unmarshal(data, band)
// Object -> JSON
str, _ := json.Marshal(band)
fmt.Println("Actual Result: ", string(str))
fmt.Println("Desired Result:", `{"Name": "AC/DC","Albums": 10}`)
// Output:
// Actual Result: {"bandname":"AC/DC","albumcount":10}
// Desired Result: {"Name": "AC/DC","Albums": 10}
}
You could implement
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
from the standard library's encoding/json package. Example:
type Band struct {
Name string `json:"bandname"`
Albums int `json:"albumcount"`
}
func (b Band) MarshalJSON() ([]byte, error) {
n, _ := json.Marshal(b.Name)
a, _ := json.Marshal(b.Albums)
return []byte(`{"Name":` + string(n) + `,"Albums":` + string(a) + `}`)
}
It's admittedly not a very nice solution, though.
As a generic solution, you could use reflection to create a new type that removes the json tags and then marshall that.
func getVariantStructValue(v reflect.Value, t reflect.Type) reflect.Value {
sf := make([]reflect.StructField, 0)
for i := 0; i < t.NumField(); i++ {
sf = append(sf, t.Field(i))
if t.Field(i).Tag.Get("json") != "" {
sf[i].Tag = ``
}
}
newType := reflect.StructOf(sf)
return v.Convert(newType)
}
func MarshalIgnoreTags(obj interface{}) ([]byte, error) {
value := reflect.ValueOf(obj)
t := value.Type()
newValue := getVariantStructValue(value, t)
return json.Marshal(newValue.Interface())
}
And you would just call it using:
str, _ := MarshalIgnoreTags(band)
Doing the opposite is a little trickier (ignore tags when unmarshalling JSON), but possible with mapstructure:
func UnmarshalIgnoreTags(data []byte, obj interface{}) error {
rv := reflect.ValueOf(obj)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return errors.New("unmarshal destination obj must be a non-nil pointer")
}
value := reflect.Indirect(rv)
t := value.Type()
newValue := getVariantStructValue(value, t)
i := newValue.Interface()
err := json.Unmarshal(data, &i)
if err == nil {
// We use mapstructure because i is of type map[string]interface{} and it's the easiest way to convert back to struct type
// See: https://stackoverflow.com/a/38939459/2516916
mapstructure.Decode(i, obj)
}
return err
}
See playground here: https://play.golang.org/p/XVYGigM71Cf
One could also use a library as mentioned in this thread: https://stackoverflow.com/a/50966527/5649638
This will give you a structs like this:
type TestJson struct {
Name string `json:"name" newtag:"newname"`
Age int `json:"age" newtag:"newage"`
}