function applyGetters(self, json) {
  const schema = self.$__schema;
  const paths = Object.keys(schema.paths);
  let i = paths.length;
  let path;
  let cur = self._doc;
  let v;

  if (!cur) {
    return json;
  }

  while (i--) {
    path = paths[i];

    const parts = path.split('.');

    const plen = parts.length;
    const last = plen - 1;
    let branch = json;
    let part;
    cur = self._doc;

    if (!self.$__isSelected(path)) {
      continue;
    }

    for (let ii = 0; ii < plen; ++ii) {
      part = parts[ii];
      v = cur[part];
      // If we've reached a non-object part of the branch, continuing would
      // cause "Cannot create property 'foo' on string 'bar'" error.
      // Necessary for mongoose-intl plugin re: gh-14446
      if (branch != null && typeof branch !== 'object') {
        break;
      } else if (ii === last) {
        branch[part] = schema.paths[path].applyGetters(
          branch[part],
          self
        );
        if (Array.isArray(branch[part]) && schema.paths[path].$embeddedSchemaType) {
          for (let i = 0; i < branch[part].length; ++i) {
            branch[part][i] = schema.paths[path].$embeddedSchemaType.applyGetters(
              branch[part][i],
              self
            );
          }
        }
      } else if (v == null) {
        if (part in cur) {
          branch[part] = v;
        }
        break;
      } else {
        branch = branch[part] || (branch[part] = {});
      }
      cur = v;
    }
  }

  return json;
}
Applies virtuals properties to `json`.

@param {Document} self
@param {Object} json
@return {Object} `json`
@api private