code: function collectMetadata(value) {
      if (value == null || typeof value !== 'object') {
        return JSON.stringify(value);
      }
      let hash;
      if (Array.isArray(value)) {
        hash = '[';
        for (let i = 0; i < value.length; i++) {
          hash += collectMetadata(value[i]) + ',';
        }
      } else {
        hash = '{';
        for (const k in value) {
          if (value.hasOwnProperty(k) && value[k] !== undefined) {
            hash += k + ':' + collectMetadata(value[k]) + ',';
          }
        }
      }
      let metadata = metadataForHash.get(hash);
      if (!metadata) {
        metadata = {value, hash, isDuplicate: false};
        metadataForHash.set(hash, metadata);
      }
      metadataForVal.set(value, metadata);
      return hash;
    }
docs: Collect common metadata for each object in the value tree, ensuring that equivalent values have the *same reference* to the same metadata. Note that the hashes generated are not exactly JSON, but still identify equivalent values. Runs in linear time due to hashing in a bottom-up recursion.