I'm trying to unmarshal some JSON from different sources that may have different keys. For instance, I may have:
{
"a": 1,
"b": 2
}
Or I may have:
{
"c": 1,
"b": 2
}
In this case, I can guarantee that "b" will be there. However, I want "a" and "c" to be represented the same way. In effect, what I want is:
type MyJson struct {
Init int `json:"a",json:"c"`
Sec int `json:"b"
}
Basically I want the unmarshaller to look for either key and set it as Init
. Now this doesn't actually work (or I wouldn't be posting). Unmarshalling the first gives me what I want, while the second sets Init
to 0. My ideal option would be to unmarshal to a struct, with one of two possibilities:
I tried to implement number 2 by making two different structs and a map, but it seems I can't make a map with a type as the second value:
type MyJson1 struct {
Init int `json:"a"`
Sec int `json:"b"`
}
type MyJson2 struct {
Init int `json:"c"`
Sec int `json:"b"`
}
Is there a way to define a set of structs that behaves like an interface? That is, they all have the same fields, but are defined differently. Or maybe there's another way. I could make the fields unmarshalling "a" and "c" other fields, then set Init
accordingly. But this doesn't scale beyond a couple of variants.
Thanks!
One possibility is to define a struct
which has a field for all the variants for your possible inputs, and for convenience provide a method for this struct which will return the field that was found in the input:
type MyJson struct {
A *int `json:"a"`
C *int `json:"c"`
Sec int `json:"b"`
}
func (j *MyJson) Init() int {
if j.A == nil {
return *j.C
}
return *j.A
}
Using it:
inputs := []string{
`{"a": 1, "b": 2}`,
`{"c": 1, "b": 2}`}
for _, input := range inputs {
var my MyJson
if err := json.Unmarshal([]byte(input), &my); err != nil {
panic(err)
}
fmt.Printf("Init: %v, Sec: %v\n", my.Init(), my.Sec)
}
Output, as expected (try it on the Go Playground):
Init: 1, Sec: 2
Init: 1, Sec: 2
In the original struct we added 2 fields for the 2 possible variants. I defined them as pointers so we can detect which one was found in the JSON input. Now if before unmarshalling we set these pointers to point to the same value, that's all we need: doesn't matter which variant of the input JSON we use, the same value will be set in memory, so you can always just read/refer the Init
struct field:
type MyJson struct {
Init *int `json:"a"`
Init2 *int `json:"c"`
Sec int `json:"b"`
}
func main() {
inputs := []string{
`{"a": 1, "b": 2}`,
`{"c": 1, "b": 2}`}
for _, input := range inputs {
var my MyJson
my.Init = new(int) // Allocate an int
my.Init2 = my.Init // Set Init2 to point to the same value
if err := json.Unmarshal([]byte(input), &my); err != nil {
panic(err)
}
fmt.Printf("Init: %v, Sec: %v\n", *my.Init, my.Sec)
}
}
Try it on the Go Playground.
You can create a function which creates and sets up your MyJson
ready for unmarshalling like this:
func NewMyJson() (my MyJson) {
my.Init = new(int) // Allocate an int
my.Init2 = my.Init // Set Init2 to point to the same value
return
}
And so using it becomes this simple:
var my = NewMyJson()
err := json.Unmarshal([]byte(input), &my)
You can specify the Init
field not to be a pointer because it is enough for Init2
to be a pointer and point to Init
, so this becomes even more simple and desirable (but then NewMyJson
must return a pointer):
type MyJson struct {
Init int `json:"a"`
Init2 *int `json:"c"`
Sec int `json:"b"`
}
func NewMyJson() *MyJson {
my := new(MyJson)
my.Init2 = &my.Init // Set Init2 to point to Init
return my
}
icza's approach is good, and better if this implementation could match stdlib's interface.
I suggest implementing json.Unmarshaler
:
// it can't get c itself.
type MyJson struct {
A int `json:"a"`
B int `json:"b"`
}
func (j *MyJson) UnmarshalJSON(b []byte) error {
type Alias MyJson
realValue := struct {
*Alias
C int `json:"c"` // <- now it can accept 'c' value
}{(*Alias)(j), 0}
if err := json.Unmarshal(b, &realValue); err != nil {
return err
} else if realValue.C != 0 {
// if C has value, overwrite A
realValue.A = realValue.C
}
return nil
}
And just decode it using json.Unmarshal
.
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