Goal: Recursively remove all empty object stubs from a document.
- 
This function removeObjectStubs will recursively prune empty objects from a given JSON document.
 - 
Requires Eventing Storage (or metadata collection) and a "source" collection.
 - 
Will operate on all documents where doc.type === "my_data_to_prune".
 - 
The removeEmptyParts function returns two (2) values in an array the doc and a flag indicating if a change has occurred. This flag ary_obj_upd[1] will increase the performance of subsequent runs where the Feed boundary is set to Everything on large document sets.
 - 
This function is a subset of the cleanup functionality found in the Scriptlet removeNullsAndEmptys.
 
- 
removeObjectStubs
 - 
Input Data/Mutation
 - 
Output Data/Mutation
 
// To run configure the settings for this Function, removeObjectStubs, as follows:
//
// Version 7.1+
//   "Function Scope"
//     *.* (or try bulk.data if non-privileged)
// Version 7.0+
//   "Listen to Location"
//     bulk.data.source
//   "Eventing Storage"
//     rr100.eventing.metadata
//   Binding(s)
//    1. "binding type", "alias name...", "bucket.scope.collection", "Access"
//       "bucket alias", "src_col",       "bulk.data.source",        "read and write"
//
// Version 6.X
//   "Source Bucket"
//     source
//   "MetaData Bucket"
//     metadata
//   Binding(s)
//    1. "binding type", "alias name...", "bucket",  "Access"
//       "bucket alias", "src_col",       "source", "read and write"
function removeEmptyParts(ary_obj_upd) {
    if (ary_obj_upd[0] !== null && typeof ary_obj_upd[0] == "object") {
        Object.entries(ary_obj_upd[0]).forEach(([k, v]) => {
            if (ary_obj_upd[0][k] && typeof ary_obj_upd[0][k] === 'object') {
                // recurse
                if (removeEmptyParts(
                    [ary_obj_upd[0][k],ary_obj_upd[1]])[1] == true) {
                    ary_obj_upd[1] = true;
                }
                // remove stub {} object items
                if (ary_obj_upd[0][k] && !Array.isArray(ary_obj_upd[0][k]) &&
                    Object.keys(ary_obj_upd[0][k]).length === 0
                ) {
                    delete(ary_obj_upd[0][k]) // 6.6+ can use "delete obj"
                    ary_obj_upd[1] = true;
                }
            }
        });
    } else {
        // obj is a number or a string
    }
    // return [ary_obj_upd[0],ary_obj_upd[1]];
    return ary_obj_upd;
}
function OnUpdate(doc, meta) {
    // optional filter to specific types
    if (doc.type !== "my_data_to_prune") return;
    // try make a new doc without {} stubs
    var altered = false;
    var ary_obj_upd = removeEmptyParts([doc,altered]);
    if (ary_obj_upd[1]) {
        // We actualy did an update so we need to write to KV
        // Note, the alias src_col (mode r+w) references the
        // handlers source collection (allowed version 6.5+).
        src_col[meta.id] = ary_obj_upd[0];
    }
}
INPUT: KEY my_data_to_prune::1001
{
  "type": "my_data_to_prune",
  "root": {
    "field1a": {},
    "field1b": {
      "field2a": {
        "value": 2
      }
    },
    "field1c": {
      "field2c": {
        "value": 3,
        "field3c": {
            "value": 4,
            "field4c": {}
        }
      }
    }
  }
}
UPDATED/OUTPUT: KEY my_data_to_prune::1001
 {
  "type": "my_data_to_prune",
  "root": {
    "field1b": {
      "field2a": {
        "value": 2
      }
    },
    "field1c": {
      "field2c": {
        "value": 3,
        "field3c": {
          "value": 4
        }
      }
    }
  }
}