From 44b01e11b72eb191877dd7627e6ed833b42b2ca2 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Wed, 24 Jun 2020 13:55:48 -0400 Subject: [PATCH] Fix map comparison of nil values and missing keys --- merge.go | 7 ++++- merge_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ v5/merge.go | 7 ++++- v5/merge_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 2 deletions(-) diff --git a/merge.go b/merge.go index 5dc6d34..14e8bb5 100644 --- a/merge.go +++ b/merge.go @@ -311,7 +311,12 @@ func matchesValue(av, bv interface{}) bool { return false } for key := range bt { - if !matchesValue(at[key], bt[key]) { + av, aOK := at[key] + bv, bOK := bt[key] + if aOK != bOK { + return false + } + if !matchesValue(av, bv) { return false } } diff --git a/merge_test.go b/merge_test.go index c87b078..cd90c56 100644 --- a/merge_test.go +++ b/merge_test.go @@ -462,6 +462,73 @@ func createNestedMap(m map[string]interface{}, depth int, objectCount *int) { } } +func TestMatchesValue(t *testing.T) { + testcases := []struct { + name string + a interface{} + b interface{} + want bool + }{ + { + name: "map empty", + a: map[string]interface{}{}, + b: map[string]interface{}{}, + want: true, + }, + { + name: "map equal keys, equal non-nil value", + a: map[string]interface{}{"1": true}, + b: map[string]interface{}{"1": true}, + want: true, + }, + { + name: "map equal keys, equal nil value", + a: map[string]interface{}{"1": nil}, + b: map[string]interface{}{"1": nil}, + want: true, + }, + + { + name: "map different value", + a: map[string]interface{}{"1": true}, + b: map[string]interface{}{"1": false}, + want: false, + }, + { + name: "map different key, matching non-nil value", + a: map[string]interface{}{"1": true}, + b: map[string]interface{}{"2": true}, + want: false, + }, + { + name: "map different key, matching nil value", + a: map[string]interface{}{"1": nil}, + b: map[string]interface{}{"2": nil}, + want: false, + }, + { + name: "map different key, first nil value", + a: map[string]interface{}{"1": true}, + b: map[string]interface{}{"2": nil}, + want: false, + }, + { + name: "map different key, second nil value", + a: map[string]interface{}{"1": nil}, + b: map[string]interface{}{"2": true}, + want: false, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + got := matchesValue(tc.a, tc.b) + if got != tc.want { + t.Fatalf("want %v, got %v", tc.want, got) + } + }) + } +} + func benchmarkMatchesValueWithDeeplyNestedFields(depth int, b *testing.B) { a := map[string]interface{}{} objCount := 1 diff --git a/v5/merge.go b/v5/merge.go index 5dc6d34..14e8bb5 100644 --- a/v5/merge.go +++ b/v5/merge.go @@ -311,7 +311,12 @@ func matchesValue(av, bv interface{}) bool { return false } for key := range bt { - if !matchesValue(at[key], bt[key]) { + av, aOK := at[key] + bv, bOK := bt[key] + if aOK != bOK { + return false + } + if !matchesValue(av, bv) { return false } } diff --git a/v5/merge_test.go b/v5/merge_test.go index c87b078..cd90c56 100644 --- a/v5/merge_test.go +++ b/v5/merge_test.go @@ -462,6 +462,73 @@ func createNestedMap(m map[string]interface{}, depth int, objectCount *int) { } } +func TestMatchesValue(t *testing.T) { + testcases := []struct { + name string + a interface{} + b interface{} + want bool + }{ + { + name: "map empty", + a: map[string]interface{}{}, + b: map[string]interface{}{}, + want: true, + }, + { + name: "map equal keys, equal non-nil value", + a: map[string]interface{}{"1": true}, + b: map[string]interface{}{"1": true}, + want: true, + }, + { + name: "map equal keys, equal nil value", + a: map[string]interface{}{"1": nil}, + b: map[string]interface{}{"1": nil}, + want: true, + }, + + { + name: "map different value", + a: map[string]interface{}{"1": true}, + b: map[string]interface{}{"1": false}, + want: false, + }, + { + name: "map different key, matching non-nil value", + a: map[string]interface{}{"1": true}, + b: map[string]interface{}{"2": true}, + want: false, + }, + { + name: "map different key, matching nil value", + a: map[string]interface{}{"1": nil}, + b: map[string]interface{}{"2": nil}, + want: false, + }, + { + name: "map different key, first nil value", + a: map[string]interface{}{"1": true}, + b: map[string]interface{}{"2": nil}, + want: false, + }, + { + name: "map different key, second nil value", + a: map[string]interface{}{"1": nil}, + b: map[string]interface{}{"2": true}, + want: false, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + got := matchesValue(tc.a, tc.b) + if got != tc.want { + t.Fatalf("want %v, got %v", tc.want, got) + } + }) + } +} + func benchmarkMatchesValueWithDeeplyNestedFields(depth int, b *testing.B) { a := map[string]interface{}{} objCount := 1