From 84138864d0f593e23c76deb3b9da430cbb0a4ab2 Mon Sep 17 00:00:00 2001
From: Kevin Hurley <kph@fb.com>
Date: Fri, 11 Sep 2015 10:25:14 -0700
Subject: [PATCH] Add ability to merge dynamic objects

Summary: Adding ability to merge dynamic objects with another object.  It will just overwrite duplicate keys

Reviewed By: @yfeldblum

Differential Revision: D2413628
---
 folly/dynamic-inl.h        | 35 +++++++++++++++++++++++++++++++++++
 folly/dynamic.h            | 17 +++++++++++++++++
 folly/test/DynamicTest.cpp | 21 +++++++++++++++++++++
 3 files changed, 73 insertions(+)

diff --git a/folly/dynamic-inl.h b/folly/dynamic-inl.h
index 6e2fd62a..ff503c4a 100644
--- a/folly/dynamic-inl.h
+++ b/folly/dynamic-inl.h
@@ -512,6 +512,41 @@ template<class K, class V> inline void dynamic::insert(K&& key, V&& val) {
   rv.first->second = std::forward<V>(val);
 }
 
+inline void dynamic::update(const dynamic& mergeObj) {
+  if (!isObject() || !mergeObj.isObject()) {
+    throw TypeError("object", type(), mergeObj.type());
+  }
+
+  for (const auto& pair : mergeObj.items()) {
+    (*this)[pair.first] = pair.second;
+  }
+}
+
+inline void dynamic::update_missing(const dynamic& mergeObj1) {
+  if (!isObject() || !mergeObj1.isObject()) {
+    throw TypeError("object", type(), mergeObj1.type());
+  }
+
+  // Only add if not already there
+  for (const auto& pair : mergeObj1.items()) {
+    if ((*this).find(pair.first) == (*this).items().end()) {
+      (*this)[pair.first] = pair.second;
+    }
+  }
+}
+
+inline dynamic dynamic::merge(
+    const dynamic& mergeObj1,
+    const dynamic& mergeObj2) {
+
+  // No checks on type needed here because they are done in update_missing
+  // Note that we do update_missing here instead of update() because
+  // it will prevent the extra writes that would occur with update()
+  auto ret = mergeObj2;
+  ret.update_missing(mergeObj1);
+  return ret;
+}
+
 inline std::size_t dynamic::erase(dynamic const& key) {
   auto& obj = get<ObjectImpl>();
   return obj.erase(key);
diff --git a/folly/dynamic.h b/folly/dynamic.h
index bea7fc44..fa494555 100644
--- a/folly/dynamic.h
+++ b/folly/dynamic.h
@@ -436,6 +436,23 @@ public:
    */
   template<class K, class V> void insert(K&&, V&& val);
 
+  /*
+   * These functions merge two folly dynamic objects.
+   * The "update" and "update_missing" functions extend the object by
+   *  inserting the key/value pairs of mergeObj into the current object.
+   *  For update, if key is duplicated between the two objects, it
+   *  will overwrite with the value of the object being inserted (mergeObj).
+   *  For "update_missing", it will prefer the value in the original object
+   *
+   * The "merge" function creates a new object consisting of the key/value
+   * pairs of both mergeObj1 and mergeObj2
+   * If the key is duplicated between the two objects,
+   *  it will prefer value in the second object (mergeObj2)
+   */
+  void update(const dynamic& mergeObj);
+  void update_missing(const dynamic& other);
+  static dynamic merge(const dynamic& mergeObj1, const dynamic& mergeObj2);
+
   /*
    * Erase an element from a dynamic object, by key.
    *
diff --git a/folly/test/DynamicTest.cpp b/folly/test/DynamicTest.cpp
index 7ba744fb..97af70be 100644
--- a/folly/test/DynamicTest.cpp
+++ b/folly/test/DynamicTest.cpp
@@ -98,6 +98,27 @@ TEST(Dynamic, ObjectBasics) {
 
   // We don't allow objects as keys in objects.
   EXPECT_ANY_THROW(newObject[d3] = 12);
+
+  // Merge two objects
+  dynamic origMergeObj1 = folly::dynamic::object();
+  dynamic mergeObj1 = origMergeObj1 = folly::dynamic::object
+    ("key1", "value1")
+    ("key2", "value2");
+  dynamic mergeObj2 = folly::dynamic::object
+    ("key2", "value3")
+    ("key3", "value4");
+  dynamic combinedObj = folly::dynamic::object
+    ("key1", "value1")
+    ("key2", "value3")
+    ("key3", "value4");
+  auto newMergeObj = dynamic::merge(mergeObj1, mergeObj2);
+  EXPECT_EQ(newMergeObj, combinedObj);
+  EXPECT_EQ(mergeObj1, origMergeObj1); // mergeObj1 should be unchanged
+
+  mergeObj1.update(mergeObj2);
+  EXPECT_EQ(mergeObj1, combinedObj);
+  dynamic arr = { 1, 2, 3, 4, 5, 6 };
+  EXPECT_THROW(mergeObj1.update(arr), std::exception);
 }
 
 TEST(Dynamic, ObjectErase) {
-- 
2.34.1