I am working with a JSON response that can sometimes return a string or an object with string keys but values that are string and bool. I understand I need to implement my own Unmarshaler for the data
Example JSON Situations:
caseOne := `"data": [
{"user": "usersName"}
]`
caseTwo := `"data": [
{"user": {"id": "usersId", "isActive": true}}
]`
My Code:
package main
type Result struct {
Data []Item `json:"data"`
}
type Item struct {
User User `json:"user"`
}
type User struct {
user string
}
func (u *User) MarshalJSON() ([]byte, error) {
return json.Marshal(u.user)
}
func (u *User) UnmarshalJSON(data []byte) error {
var raw interface{}
json.Unmarshal(data, &raw)
switch raw := raw.(type) {
case string:
*u = User{raw}
case map[string]interface{}:
// how do I handle the other "isActive" key that is map[string]bool?
*u = User{raw["id"].(string)}
}
return nil
}
This question/answer: Here comes close to answering my use case but I am a bit stuck on how to handle multiple map values of different value types.
Current Go Playground: Here
type User struct {
Id string `json:"id"`
Name string `json:"name"`
IsActive bool `json:"isActive"`
}
func (u User) MarshalJSON() ([]byte, error) {
if u == (User{Name: u.Name}) { // check if u contains only name
return json.Marshal(u.Name)
}
type U User
return json.Marshal(U(u))
}
func (u *User) UnmarshalJSON(data []byte) error {
switch data[0] {
case '"': // string?
return json.Unmarshal(data, &u.Name)
case '{': // object?
type U User
return json.Unmarshal(data, (*U)(u))
}
return fmt.Errorf("unsupported JSON: %s", string(data))
}
https://go.dev/play/p/toOIz0XOQUo
If you pass u
directly to json.Marshal
from inside MarshalJSON
, or if you pass it directly to json.Unmarshal
from inside UnmarshalJSON
, your program will get stuck in infinite recursion and eventually overflow the stack since MarshalJSON/UnmarshalJSON
are called automatically by json.Marshal/json.Unmarshal
on any value that implements those methods.
Type U
is used to avoid this problem.
The statement type U User
declares a new type U
with its underlying type identical to that of User
. Because the underlying types are identical we can easily convert one type to the other and back. However, the type declaration statement does not "carry over" the methods from the old type to the new type, so the new type U
has none of the methods previously declared on User
and therefore json.Marshal/json.Unmarshal
will not get stuck in infinite call recursion anymore.