Description
When structuring json types, it would be very helpful to allow the custom types define if the value is to be omitted. This is similar to a javascript value may set to "undefined".
Just as we allow custom types to implement "MarshalJSON" to provide a custom value: ("null", multivariate, etc). So too should we allow these custom types to be set to "Omitted" as well.
This proposal may supersede other highly desired omitnil
proposals as one may leverage this abstraction to accomplish the same goal.
Proposal
Update encoding/json
to check if a struct field implements an Omittable
interface where Omittable
is defined as follows:
type Omittable struct{
Omitted() (bool, error)
}
If a fieldType implements this interface, omit the filed if the response of Omitted() is true.
Example (Omittable String)
type Foo struct{
Field1 OmittableString `json:"field_1"`
}
type OmittableString struct {
IsSet bool
Value string
}
func (o OmittableString) UnmarshalJSON(input []byte) error {o.IsSet=true; return json.Unmarshal(input, o.Value)}
func (o OmittableString) MarshalJSON() ([]byte, error) {return json.Marshal(o.Value)}
func (o OmittableString) Omitted() (bool, error) {return !o.IsSet, nil}
x := &Foo{}
_ = json.Unmarshal([]byte(`{}`), x)
// x.Field1.IsSet = false
out, _ = json.Marshal(x)
// {}
_ = json.Unmarshal([]byte(`{"field_1": ""}`), x)
// x.Field1.IsSet = true
// x.Field1.Value = ""
out, _ = json.Marshal(x)
// {"field_1": ""}
_ = json.Unmarshal([]byte(`{"field_1": "foo"}`), x)
// x.Field1.IsSet = true
// x.Field1.Value = "foo"
out, _ = json.Marshal(x)
// {"field_1": "foo"}
Example (Omit-nil)
type Foo struct{
Field1 OmitNilStruct `json:"field_1"`
}
type OmitNilStruct struct {
Value *MyStruct
}
func (o OmitNilStruct) UnmarshalJSON(input []byte) error {return json.Unmarshal(input, o.Value)}
func (o OmitNilStruct) MarshalJSON() ([]byte, error) {return json.Marshal(o.Value)}
func (o OmitNilStruct) Omitted() (bool, error) {return o.Value == nil, nil}
type MyStruct struct {}
x := &Foo{}
_ = json.Unmarshal([]byte(`{}`), x)
// x.Field1.Value = nil
out, _ = json.Marshal(x)
// {}
_ = json.Unmarshal([]byte(`{"field_1": {}}`), x)
// x.Field1.Value = MyStruct{}
out, _ = json.Marshal(x)
// {"field_1": ""}
Compatibility
This may cause issues with existing custom types that happen to implement this new method. Though I would guess that the number of uses with such a pattern is low.
Implementations
I've implemented this locally and it seems to work fine. If approved I can submit a PR.
Related Proposals
- proposal: encoding/json, encoding/xml: support zero values of structs with omitempty #11939
- proposal: encoding/json: add omitnil option #22480
- proposal: encoding/json: add interface for omitempty #32675
- encoding/json: add omitzero option #45669
- encoding/json: Proposal - Flexible omitempty #45787
- proposal: encoding/json: allow returning nil from MarshalJSON to omit the field #50480