Skip to content

Commit 78d6b85

Browse files
committed
add custom handling of NullFields to marshaling
1 parent 4b7b22a commit 78d6b85

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

response.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ func visitModelNode(model interface{}, included *map[string]*Node,
233233

234234
modelValue := value.Elem()
235235
modelType := value.Type().Elem()
236+
var nullFields *[]string
237+
if _, ok := modelType.FieldByName("NullFields"); ok {
238+
nullFields = modelValue.FieldByName("NullFields").Interface().(*[]string)
239+
}
236240

237241
for i := 0; i < modelValue.NumField(); i++ {
238242
fieldValue := modelValue.Field(i)
@@ -348,15 +352,15 @@ func visitModelNode(model interface{}, included *map[string]*Node,
348352
} else if fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
349353
// A time pointer may be nil
350354
if fieldValue.IsNil() {
351-
if omitEmpty {
355+
if omitEmpty && !stringInSlice(nullFields, structField.Name) {
352356
continue
353357
}
354358

355359
node.Attributes[args[1]] = nil
356360
} else {
357361
tm := fieldValue.Interface().(*time.Time)
358362

359-
if tm.IsZero() && omitEmpty {
363+
if tm.IsZero() && omitEmpty && !stringInSlice(nullFields, structField.Name) {
360364
continue
361365
}
362366

@@ -373,7 +377,7 @@ func visitModelNode(model interface{}, included *map[string]*Node,
373377
emptyValue := reflect.Zero(fieldValue.Type())
374378

375379
// See if we need to omit this field
376-
if omitEmpty && reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) {
380+
if omitEmpty && reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) && !stringInSlice(nullFields, structField.Name) {
377381
continue
378382
}
379383

@@ -648,3 +652,16 @@ func convertToSliceInterface(i *interface{}) ([]interface{}, error) {
648652
}
649653
return response, nil
650654
}
655+
656+
func stringInSlice(s *[]string, v string) bool {
657+
if s == nil {
658+
return false
659+
}
660+
661+
for _, field := range *s {
662+
if field == v {
663+
return true
664+
}
665+
}
666+
return false
667+
}

response_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,47 @@ func TestWithOmitsEmptyAnnotationOnAttribute(t *testing.T) {
518518
}
519519
}
520520

521+
func TestOmitsEmptyAnnotationAndNullFieldOnAttribute(t *testing.T) {
522+
type Author struct{}
523+
524+
type Book struct {
525+
ID int `jsonapi:"primary,books"`
526+
Name string `jsonapi:"attr,name"`
527+
Author *Author `jsonapi:"attr,author,omitempty"`
528+
PublishedAt *time.Time `jsonapi:"attr,published_at,omitempty"`
529+
530+
NullFields *[]string
531+
}
532+
533+
book := &Book{
534+
ID: 123,
535+
Name: "The Confidence Man",
536+
PublishedAt: nil,
537+
538+
NullFields: &[]string{"PublishedAt"},
539+
}
540+
541+
out := bytes.NewBuffer(nil)
542+
if err := MarshalPayload(out, book); err != nil {
543+
t.Fatal(err)
544+
}
545+
546+
var jsonData map[string]interface{}
547+
if err := json.Unmarshal(out.Bytes(), &jsonData); err != nil {
548+
t.Fatal(err)
549+
}
550+
551+
payload := jsonData["data"].(map[string]interface{})
552+
attributes := payload["attributes"].(map[string]interface{})
553+
if _, ok := attributes["published_at"]; !ok {
554+
t.Fatal("Was expecting the data.attributes.published_at to have NOT been omitted")
555+
}
556+
557+
if _, ok := attributes["author"]; ok {
558+
t.Fatal("Was expecting the data.attributes.author to have been omitted")
559+
}
560+
}
561+
521562
func TestMarshalIDPtr(t *testing.T) {
522563
id, make, model := "123e4567-e89b-12d3-a456-426655440000", "Ford", "Mustang"
523564
car := &Car{

0 commit comments

Comments
 (0)