From 1bf031b5f2458618dfddbe13c6916fd369d17fe1 Mon Sep 17 00:00:00 2001 From: Taylor Chaparro Date: Tue, 19 Dec 2023 12:40:31 -0800 Subject: [PATCH] add custom handling of `NullFields` to marshaling --- response.go | 19 +++++++++++++++++-- response_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/response.go b/response.go index 602b16b..6707744 100644 --- a/response.go +++ b/response.go @@ -233,6 +233,12 @@ func visitModelNode(model interface{}, included *map[string]*Node, modelValue := value.Elem() modelType := value.Type().Elem() + nullFields := []string{} + if _, ok := modelType.FieldByName("NullFields"); ok { + if modelValue.FieldByName("NullFields").Kind() == reflect.Slice { + nullFields = modelValue.FieldByName("NullFields").Interface().([]string) + } + } for i := 0; i < modelValue.NumField(); i++ { fieldValue := modelValue.Field(i) @@ -348,7 +354,7 @@ func visitModelNode(model interface{}, included *map[string]*Node, } else if fieldValue.Type() == reflect.TypeOf(new(time.Time)) { // A time pointer may be nil if fieldValue.IsNil() { - if omitEmpty { + if omitEmpty && !stringInSlice(nullFields, structField.Name) { continue } @@ -373,7 +379,7 @@ func visitModelNode(model interface{}, included *map[string]*Node, emptyValue := reflect.Zero(fieldValue.Type()) // See if we need to omit this field - if omitEmpty && reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) { + if omitEmpty && reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) && !stringInSlice(nullFields, structField.Name) { continue } @@ -648,3 +654,12 @@ func convertToSliceInterface(i *interface{}) ([]interface{}, error) { } return response, nil } + +func stringInSlice(s []string, v string) bool { + for _, field := range s { + if field == v { + return true + } + } + return false +} diff --git a/response_test.go b/response_test.go index 599d999..400b62c 100644 --- a/response_test.go +++ b/response_test.go @@ -518,6 +518,47 @@ func TestWithOmitsEmptyAnnotationOnAttribute(t *testing.T) { } } +func TestOmitsEmptyAnnotationAndNullFieldOnAttribute(t *testing.T) { + type Author struct{} + + type Book struct { + ID int `jsonapi:"primary,books"` + Name string `jsonapi:"attr,name"` + Author *Author `jsonapi:"attr,author,omitempty"` + PublishedAt *time.Time `jsonapi:"attr,published_at,omitempty"` + + NullFields []string + } + + book := &Book{ + ID: 123, + Name: "The Confidence Man", + PublishedAt: nil, + + NullFields: []string{"PublishedAt"}, + } + + out := bytes.NewBuffer(nil) + if err := MarshalPayload(out, book); err != nil { + t.Fatal(err) + } + + var jsonData map[string]interface{} + if err := json.Unmarshal(out.Bytes(), &jsonData); err != nil { + t.Fatal(err) + } + + payload := jsonData["data"].(map[string]interface{}) + attributes := payload["attributes"].(map[string]interface{}) + if _, ok := attributes["published_at"]; !ok { + t.Fatal("Was expecting the data.attributes.published_at to have NOT been omitted") + } + + if _, ok := attributes["author"]; ok { + t.Fatal("Was expecting the data.attributes.author to have been omitted") + } +} + func TestMarshalIDPtr(t *testing.T) { id, make, model := "123e4567-e89b-12d3-a456-426655440000", "Ford", "Mustang" car := &Car{