From 10a1c0a04bcff0237cb9134b44f0db143e30345b Mon Sep 17 00:00:00 2001 From: Max Goncharenko Date: Thu, 13 May 2021 18:16:21 +0300 Subject: [PATCH] MergeMergePatches preserve nulls in complex objects Signed-off-by: Max Goncharenko --- merge.go | 5 ++++- merge_test.go | 20 +++++++++++++------- v5/merge.go | 4 +++- v5/merge_test.go | 20 +++++++++++++------- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/merge.go b/merge.go index 7c5fadc..f23fd58 100644 --- a/merge.go +++ b/merge.go @@ -38,7 +38,10 @@ func mergeDocs(doc, patch *partialDoc, mergeMerge bool) { cur, ok := (*doc)[k] if !ok || cur == nil { - pruneNulls(v) + if !mergeMerge { + pruneNulls(v) + } + (*doc)[k] = v } else { (*doc)[k] = merge(cur, v, mergeMerge) diff --git a/merge_test.go b/merge_test.go index 0acc17d..672955d 100644 --- a/merge_test.go +++ b/merge_test.go @@ -454,7 +454,7 @@ func createNestedMap(m map[string]interface{}, depth int, objectCount *int) { if depth == 0 { return } - for i := 0; i< 2;i++ { + for i := 0; i < 2; i++ { nested := map[string]interface{}{} *objectCount += 1 createNestedMap(nested, depth-1, objectCount) @@ -546,12 +546,12 @@ func benchmarkMatchesValueWithDeeplyNestedFields(depth int, b *testing.B) { func BenchmarkMatchesValue1(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(1, b) } func BenchmarkMatchesValue2(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(2, b) } func BenchmarkMatchesValue3(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(3, b) } -func BenchmarkMatchesValue4(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(4, b) } -func BenchmarkMatchesValue5(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(5, b) } -func BenchmarkMatchesValue6(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(6, b) } -func BenchmarkMatchesValue7(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(7, b) } -func BenchmarkMatchesValue8(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(8, b) } -func BenchmarkMatchesValue9(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(9, b) } +func BenchmarkMatchesValue4(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(4, b) } +func BenchmarkMatchesValue5(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(5, b) } +func BenchmarkMatchesValue6(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(6, b) } +func BenchmarkMatchesValue7(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(7, b) } +func BenchmarkMatchesValue8(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(8, b) } +func BenchmarkMatchesValue9(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(9, b) } func BenchmarkMatchesValue10(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(10, b) } func TestCreateMergePatchComplexRemoveAll(t *testing.T) { @@ -649,6 +649,12 @@ func TestMergeMergePatches(t *testing.T) { p2: `{"del2": null}`, exp: `{"del1": null, "del2": null}`, }, + { + demonstrates: "nulls are kept in complex objects", + p1: `{}`, + p2: `{"request":{"object":{"complex_object_array":["value1","value2","value3"],"complex_object_map":{"key1":"value1","key2":"value2","key3":"value3"},"simple_object_bool":false,"simple_object_float":-5.5,"simple_object_int":5,"simple_object_null":null,"simple_object_string":"example"}}}`, + exp: `{"request":{"object":{"complex_object_array":["value1","value2","value3"],"complex_object_map":{"key1":"value1","key2":"value2","key3":"value3"},"simple_object_bool":false,"simple_object_float":-5.5,"simple_object_int":5,"simple_object_null":null,"simple_object_string":"example"}}}`, + }, { demonstrates: "a key added then deleted is kept deleted", p1: `{"add_then_delete": "atd"}`, diff --git a/v5/merge.go b/v5/merge.go index 7219316..f8a176b 100644 --- a/v5/merge.go +++ b/v5/merge.go @@ -48,7 +48,9 @@ func mergeDocs(doc, patch *partialDoc, mergeMerge bool) { cur, ok := doc.obj[k] if !ok || cur == nil { - pruneNulls(v) + if !mergeMerge { + pruneNulls(v) + } _ = doc.set(k, v, &ApplyOptions{}) } else { _ = doc.set(k, merge(cur, v, mergeMerge), &ApplyOptions{}) diff --git a/v5/merge_test.go b/v5/merge_test.go index 8006045..0a62bb8 100644 --- a/v5/merge_test.go +++ b/v5/merge_test.go @@ -454,7 +454,7 @@ func createNestedMap(m map[string]interface{}, depth int, objectCount *int) { if depth == 0 { return } - for i := 0; i< 2;i++ { + for i := 0; i < 2; i++ { nested := map[string]interface{}{} *objectCount += 1 createNestedMap(nested, depth-1, objectCount) @@ -546,12 +546,12 @@ func benchmarkMatchesValueWithDeeplyNestedFields(depth int, b *testing.B) { func BenchmarkMatchesValue1(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(1, b) } func BenchmarkMatchesValue2(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(2, b) } func BenchmarkMatchesValue3(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(3, b) } -func BenchmarkMatchesValue4(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(4, b) } -func BenchmarkMatchesValue5(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(5, b) } -func BenchmarkMatchesValue6(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(6, b) } -func BenchmarkMatchesValue7(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(7, b) } -func BenchmarkMatchesValue8(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(8, b) } -func BenchmarkMatchesValue9(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(9, b) } +func BenchmarkMatchesValue4(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(4, b) } +func BenchmarkMatchesValue5(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(5, b) } +func BenchmarkMatchesValue6(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(6, b) } +func BenchmarkMatchesValue7(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(7, b) } +func BenchmarkMatchesValue8(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(8, b) } +func BenchmarkMatchesValue9(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(9, b) } func BenchmarkMatchesValue10(b *testing.B) { benchmarkMatchesValueWithDeeplyNestedFields(10, b) } func TestCreateMergePatchComplexRemoveAll(t *testing.T) { @@ -649,6 +649,12 @@ func TestMergeMergePatches(t *testing.T) { p2: `{"del2": null}`, exp: `{"del1": null, "del2": null}`, }, + { + demonstrates: "nulls are kept in complex objects", + p1: `{}`, + p2: `{"request":{"object":{"complex_object_array":["value1","value2","value3"],"complex_object_map":{"key1":"value1","key2":"value2","key3":"value3"},"simple_object_bool":false,"simple_object_float":-5.5,"simple_object_int":5,"simple_object_null":null,"simple_object_string":"example"}}}`, + exp: `{"request":{"object":{"complex_object_array":["value1","value2","value3"],"complex_object_map":{"key1":"value1","key2":"value2","key3":"value3"},"simple_object_bool":false,"simple_object_float":-5.5,"simple_object_int":5,"simple_object_null":null,"simple_object_string":"example"}}}`, + }, { demonstrates: "a key added then deleted is kept deleted", p1: `{"add_then_delete": "atd"}`,