From: Filippo Valsorda <filippo.valsorda@gmail.com>
Date: Sat, 8 Mar 2014 02:56:05 +0000 (+0100)
Subject: Add a recursive object merge strategy and bind it to *
X-Git-Tag: jq-1.4~34
X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=refs%2Fpull%2F321%2Fhead;p=jq

Add a recursive object merge strategy and bind it to *

This commit adds a jv_object_merge_recursive function, that performs
recursive object merging, and binds it to multiply when applied to
two objects.

Added docs and tests.

Closes #320
---

diff --git a/builtin.c b/builtin.c
index 61a9fed..9881dc1 100644
--- a/builtin.c
+++ b/builtin.c
@@ -172,6 +172,8 @@ static jv f_multiply(jv input, jv a, jv b) {
       return jv_null();
     }
     return res;
+  } else if (jv_get_kind(a) == JV_KIND_OBJECT && jv_get_kind(b) == JV_KIND_OBJECT) {
+    return jv_object_merge_recursive(a, b);
   } else {
     return type_error2(a, b, "cannot be multiplied");
   }  
diff --git a/docs/content/3.manual/manual.yml b/docs/content/3.manual/manual.yml
index a175b05..b30ba6c 100644
--- a/docs/content/3.manual/manual.yml
+++ b/docs/content/3.manual/manual.yml
@@ -479,7 +479,8 @@ sections:
           - **Objects** are added by merging, that is, inserting all
               the key-value pairs from both objects into a single
               combined object. If both objects contain a value for the
-              same key, the object on the right of the `+` wins.
+              same key, the object on the right of the `+` wins. (For
+              recursive merge use the `*` operator.)
           
           `null` can be added to any value, and returns the other
           value unchanged.
@@ -527,6 +528,11 @@ sections:
           Dividing a string by another splits the first using the second
           as separators.
 
+          Multiplying two objects will merge them recursively: this works
+          like addition but if both objects contain a value for the
+          same key, and the values are objects, the two are merged with
+          the same strategy.
+
         examples:
           - program: '10 / . * 3'
             input: 5
@@ -534,6 +540,9 @@ sections:
           - program: '. / ", "'
             input: '"a, b,c,d, e"'
             output: ['["a","b,c,d","e"]']
+          - program: '{"k": {"a": 1, "b": 2}} * {"k": {"a": 0,"c": 3}}'
+            input: 'null'
+            output: ['{"k": {"a": 0, "b": 2, "c": 3}}']
 
       - title: `length`
         body: |
diff --git a/jv.c b/jv.c
index 2530d7f..e969b12 100644
--- a/jv.c
+++ b/jv.c
@@ -1106,6 +1106,25 @@ jv jv_object_merge(jv a, jv b) {
   return a;
 }
 
+jv jv_object_merge_recursive(jv a, jv b) {
+  assert(jv_get_kind(a) == JV_KIND_OBJECT);
+  assert(jv_get_kind(b) == JV_KIND_OBJECT);
+
+  jv_object_foreach(b, k, v) {
+    jv elem = jv_object_get(jv_copy(a), jv_copy(k));
+    if (jv_is_valid(elem) &&
+        jv_get_kind(elem) == JV_KIND_OBJECT &&
+        jv_get_kind(v) == JV_KIND_OBJECT) {
+      a = jv_object_set(a, k, jv_object_merge_recursive(elem, v));
+    } else {
+      jv_free(elem);
+      a = jv_object_set(a, k, v);
+    }
+  }
+  jv_free(b);
+  return a;
+}
+
 int jv_object_contains(jv a, jv b) {
   assert(jv_get_kind(a) == JV_KIND_OBJECT);
   assert(jv_get_kind(b) == JV_KIND_OBJECT);
diff --git a/jv.h b/jv.h
index 6e82b4c..f58690a 100644
--- a/jv.h
+++ b/jv.h
@@ -104,6 +104,7 @@ jv jv_object_set(jv object, jv key, jv value);
 jv jv_object_delete(jv object, jv key);
 int jv_object_length(jv object);
 jv jv_object_merge(jv, jv);
+jv jv_object_merge_recursive(jv, jv);
 
 int jv_object_iter(jv);
 int jv_object_iter_next(jv, int);
diff --git a/tests/all.test b/tests/all.test
index e2b77ed..ff7245b 100644
--- a/tests/all.test
+++ b/tests/all.test
@@ -779,3 +779,15 @@ null
 map([1,2][0:.])
 [-1, 1, 2, 3, 1000000000000000000]
 [[1], [1], [1,2], [1,2], [1,2]]
+
+{"k": {"a": 1, "b": 2}} * .
+{"k": {"a": 0,"c": 3}}
+{"k": {"a": 0, "b": 2, "c": 3}}
+
+{"k": {"a": 1, "b": 2}, "hello": {"x": 1}} * .
+{"k": {"a": 0,"c": 3}, "hello": 1}
+{"k": {"a": 0, "b": 2, "c": 3}, "hello": 1}
+
+{"k": {"a": 1, "b": 2}, "hello": 1} * .
+{"k": {"a": 0,"c": 3}, "hello": {"x": 1}}
+{"k": {"a": 0, "b": 2, "c": 3}, "hello": {"x": 1}}