type MyObj struct {
Field1 string `json:"field_1"`
Field2 int64 `json:"field_2"`
Field3 string `json:"field_3"`
...
FieldK string `json:"field_k"`
FieldN MyInterface `json:"field_n"`
}
I have a model in my code that (except for the irrelevant domain details) looks like this. The idea of the FieldN
field is to support two types, say, MyType1
and MyType2
. These have the same CommonMethod()
but the models are very different so it’s not about having a parent type with more fields.
Quite expectedly, Go is unable to unmarshal JSON into an interface value. I am trying to use a custom UnmarshalJSON()
implementation but so far it looks really awkward:
func (m *MyObj) UnmarshalJSON(data []byte) error {
out := &MyObj{}
var m map[string]json.RawMessage
if err := json.Unmarshal(data, &m); err != nil {
return err
}
if err := json.Unmarshal(m["field_1"], &out.Field1); err != nil {
return err
}
delete(m, "field_1")
if err := json.Unmarshal(m["field_2"], &out.Field2); err != nil {
return err
}
delete(m, "field_2")
if err := json.Unmarshal(m["field_3"], &out.Field3); err != nil {
return err
}
delete(m, "field_3")
... // from 3 to k-1
if err := json.Unmarshal(m["field_k"], &out.FieldK); err != nil {
return err
}
delete(m, "field_k")
var mt1 MyType1
if err := json.Unmarshal(m["field_n"], &mt1); err == nil {
s.FieldN = &mt1
return nil
}
var mt2 MyType2
if err := json.Unmarshal(m["field_n"], &mt2); err == nil {
s.FieldN = &mt2
return nil
}
return nil
}
The idea of this approach is to first unmarshal all "static" values and then deal with the interface type. There are at least 2 problems, however, with it, in my opinion:
-
In my case, the number of fields might grow in the future and the code will get even more repetitive than it currently is
-
Even the current version requires checking that the map
m
has keyfield_i
otherwise I would just getunexpected end of input
. This is even more cumbersome.
Is there a more elegant way to do the following:
- Unmarshal all fields with static types
- Handle the only special interface-typed value
Thanks!
Important update:
It should be noted that Field1
effectively defines which concrete type should be used for FieldN
. This, as was noted in the comments, should simplify the approach considerably but I still struggle a bit with the correct implementation.
2
Answers
This demo is based on @mkopriva’s suggestion (
DisallowUnknownFields
) but still use the"try one; if failed, try another"
procedure.The output:
Use json.RawMessage to capture the varying part of the object. Decode the raw message using type determined in application logic.
https://go.dev/play/p/hV3Lgn1RkBz