Based on your question, I'm assuming all fields (even the ones with the same name) have a cloud. or local. prefix in the JSON tags. If that's the case, you can simply embed both options into a single type:
type Wrapper struct {
*CloudSettings
*LocalSettings
}
Then unmarshal into this wrapper type. The JSON tags will ensure the correct field on the correct settings type are populated:
wrapper := &Wrapper{}
if err := json.NewDecoder(request.Body).Decode(&wrapper); err != nil {
// handle
}
// now to work out which settings were passed:
if wrapper.CloudSettings == nil {
fmt.Println("Local settings provided!")
// use wrapper.CloudSettings
} else {
fmt.Println("Cloud settings provided!")
// use wrapper.LocalSettings
}
You mention that we expect to see local settings loaded based on a header value. You can simply unmarshal the payload, and then check whether the header matches the settings type that was loaded. If the header specified local settings, but the payload contained cloud settings, simply return an error response.
Still, I'm assuming here that your JSON tags will be different for both setting types. That doesn't always apply, so if my assumption is incorrect, and some fields share the same JSON tags, then a custom Unmarshal function would be the way to go:
func (w *Wrapper) UnmarshalJSON(data []byte) error {
// say we want to prioritise Local over cloud:
l := LocalSettings{}
if err := json.Unmarshal(data, &l); err == nil {
// we could unmarshal into local without a hitch?
w.CloudSettings = nil // ensure this is blanked out
w.LocalSettings = &l // set local
return nil
}
// we should use cloud settings
c := CloudSettings{}
if err := json.Unmarshal(data, &c); err != nil {
return err
}
w.LocalSettings = nil
w.CloudSettings = &c
return nil
}
This way, any conflicts are taken care of, and we can control which settings take precedence. Again, regardless of the outcome of the JSON unmarshalling, you can simply cross check the header value + which settings type was populated, and take it from there.
Lastly, if there's a sizeable overlap between both settings types, you could just as well unmarshal the payload into both types, and populate both fields in the wrapper type:
func (w *Wrapper) UnmarshalJSON(data []byte) error {
*w = Wrapper{} // make sure we start with a clean slate
l := LocalSettings{}
var localFail err
if err := json.Unmarshal(data, &l); err == nil {
w.LocalSettings = &l // set local
} else {
localFail = err
}
c := CloudSettings{}
if err := json.Unmarshal(data, &c); err == nil {
w.CloudSettings = &c
} else if localFail != nil { // both unmarshal calls failed
return err // maybe wrap/return custom error
}
return nil // one or more unmarshals were successful
}
That should do the trick