function valueFilter(val, assignmentOpts, populateOptions, allIds) {
  const userSpecifiedTransform = typeof populateOptions.transform === 'function';
  const transform = userSpecifiedTransform ? populateOptions.transform : v => v;
  if (Array.isArray(val)) {
    // find logic
    const ret = [];
    const numValues = val.length;
    for (let i = 0; i < numValues; ++i) {
      let subdoc = val[i];
      const _allIds = Array.isArray(allIds) ? allIds[i] : allIds;
      if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null) && !userSpecifiedTransform) {
        continue;
      } else if (!populateOptions.retainNullValues && subdoc == null) {
        continue;
      } else if (userSpecifiedTransform) {
        subdoc = transform(isPopulatedObject(subdoc) ? subdoc : null, _allIds);
      }
      maybeRemoveId(subdoc, assignmentOpts);
      ret.push(subdoc);
      if (assignmentOpts.originalLimit &&
          ret.length >= assignmentOpts.originalLimit) {
        break;
      }
    }

    const rLen = ret.length;
    // Since we don't want to have to create a new mongoosearray, make sure to
    // modify the array in place
    while (val.length > rLen) {
      Array.prototype.pop.apply(val, []);
    }
    let i = 0;
    if (utils.isMongooseArray(val)) {
      for (i = 0; i < rLen; ++i) {
        val.set(i, ret[i], true);
      }
    } else {
      for (i = 0; i < rLen; ++i) {
        val[i] = ret[i];
      }
    }
    return val;
  }

  // findOne
  if (isPopulatedObject(val) || utils.isPOJO(val)) {
    maybeRemoveId(val, assignmentOpts);
    return transform(val, allIds);
  }
  if (val instanceof Map) {
    return val;
  }

  if (populateOptions.justOne === false) {
    return [];
  }

  return val == null ? transform(val, allIds) : transform(null, allIds);
}
1) Apply backwards compatible find/findOne behavior to sub documents

   find logic:
     a) filter out non-documents
     b) remove _id from sub docs when user specified

   findOne
     a) if no doc found, set to null
     b) remove _id from sub docs when user specified

2) Remove _ids when specified by users query.

background:
_ids are left in the query even when user excludes them so
that population mapping can occur.
@param {Any} val
@param {Object} assignmentOpts
@param {Object} populateOptions
@param {Function} [populateOptions.transform]
@param {Boolean} allIds
@api private