Skip to content

Commit

Permalink
feat: introduce Omissible (OmitJSONry) interface
Browse files Browse the repository at this point in the history
  • Loading branch information
blgm committed Jun 16, 2020
1 parent 6dca3c3 commit bc9f837
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 3 deletions.
6 changes: 6 additions & 0 deletions jsonry_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ func (i *implementsJSONUnmarshaler) UnmarshalJSON(input []byte) error {
return nil
}

type implementsOmissible string

func (i implementsOmissible) OmitJSONry() bool {
return i == "omit"
}

type nullString struct {
value string
null bool
Expand Down
21 changes: 18 additions & 3 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import (
// When a field is a slice or an array, a single list hint "[]" may be specified in the JSONry path so that the array
// is created at the correct position in the JSON output.
//
// If a field implements the json.Marshaler interface, then the MarshalJSON() method will be called.
// If a type implements the json.Marshaler interface, then the MarshalJSON() method will be called.
//
// If a type implements the jsonry.Omissible interface, then the OmitJSONry() method will be used to
// to determine whether or not to marshal the field, overriding any `,omitempty` tags.
//
// The field type can be string, bool, int*, uint*, float*, map, slice, array or struct. JSONry is recursive.
func Marshal(in interface{}) ([]byte, error) {
Expand All @@ -44,9 +47,8 @@ func marshalStruct(ctx context.Context, in reflect.Value) (map[string]interface{

if public(f) {
p := path.ComputePath(f)
shouldSkip := p.OmitAlways || (p.OmitEmpty && isEmpty(in.Field(i)))

if !shouldSkip {
if shouldMarshal(p, in.Field(i)) {
r, err := marshal(ctx.WithField(f.Name, f.Type), in.Field(i))
if err != nil {
return nil, err
Expand Down Expand Up @@ -139,6 +141,19 @@ func marshalJSONMarshaler(ctx context.Context, in reflect.Value) (interface{}, e
return r, nil
}

func shouldMarshal(p path.Path, v reflect.Value) bool {
switch {
case p.OmitAlways:
return false
case v.Type().Implements(reflect.TypeOf((*Omissible)(nil)).Elem()):
return !v.MethodByName("OmitJSONry").Call(nil)[0].Bool()
case p.OmitEmpty && isEmpty(v):
return false
default:
return true
}
}

func isEmpty(v reflect.Value) bool {
k := v.Kind()
switch {
Expand Down
31 changes: 31 additions & 0 deletions marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,37 @@ var _ = Describe("Marshal", func() {
})
})

Describe("Omissible interface", func() {
It("can choose to be marshaled", func() {
s := struct {
A implementsOmissible
}{
A: "",
}
expectToMarshal(s, `{"A": ""}`)
})

It("can choose not to be marshaled", func() {
s := struct {
A implementsOmissible
}{
A: "omit",
}
expectToMarshal(s, `{}`)
})

It("overrides omitempty", func() {
s := struct {
A implementsOmissible `jsonry:",omitempty"`
B string `jsonry:",omitempty"`
}{
A: "",
B: "",
}
expectToMarshal(s, `{"A": ""}`)
})
})

Describe("inputs", func() {
It("accept a struct", func() {
var s struct{}
Expand Down
10 changes: 10 additions & 0 deletions omissible.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package jsonry

// Omissible is the interface implemented by types that indicate
// whether they should be omitted when being marshaled. It
// allows for more custom control than the `omitempty` tag.
// This interface overrides any `omitempty` behavior, and it is
// not necessary to specify `omitempty` with an Omissible type.
type Omissible interface {
OmitJSONry() bool
}

0 comments on commit bc9f837

Please sign in to comment.