set(key, value) {
    if (isBsonType(key, 'ObjectId')) {
      key = key.toString();
    }

    checkValidKey(key);
    value = handleSpreadDoc(value);

    // Weird, but because you can't assign to `this` before calling `super()`
    // you can't get access to `$__schemaType` to cast in the initial call to
    // `set()` from the `super()` constructor.

    if (this.$__schemaType == null) {
      this.$__deferred = this.$__deferred || [];
      this.$__deferred.push({ key: key, value: value });
      return;
    }

    let _fullPath;
    const parent = this.$__parent;
    const populated = parent != null && parent.$__ && parent.$__.populated ?
      parent.$populated(fullPath.call(this), true) || parent.$populated(this.$__path, true) :
      null;
    const priorVal = this.get(key);

    if (populated != null) {
      if (this.$__schemaType.$isSingleNested) {
        throw new MongooseError(
          'Cannot manually populate single nested subdoc underneath Map ' +
          `at path "${this.$__path}". Try using an array instead of a Map.`
        );
      }
      if (Array.isArray(value) && this.$__schemaType.$isMongooseArray) {
        value = value.map(v => {
          if (v.$__ == null) {
            v = new populated.options[populateModelSymbol](v);
          }
          // Doesn't support single nested "in-place" populate
          v.$__.wasPopulated = { value: v._doc._id };
          return v;
        });
      } else if (value != null) {
        if (value.$__ == null) {
          value = new populated.options[populateModelSymbol](value);
        }
        // Doesn't support single nested "in-place" populate
        value.$__.wasPopulated = { value: value._doc._id };
      }
    } else {
      try {
        const options = this.$__schemaType.$isMongooseDocumentArray || this.$__schemaType.$isSingleNested || this.$__schemaType.$isMongooseArray || this.$__schemaType.$isSchemaMap ?
          { path: fullPath.call(this) } :
          null;
        value = this.$__schemaType.applySetters(
          value,
          this.$__parent,
          false,
          this.get(key),
          options
        );
      } catch (error) {
        if (this.$__parent != null && this.$__parent.$__ != null) {
          this.$__parent.invalidate(fullPath.call(this), error);
          return;
        }
        throw error;
      }
    }

    super.set(key, value);

    if (parent != null && parent.$__ != null && !deepEqual(value, priorVal)) {
      const path = fullPath.call(this);
      parent.markModified(path);
      // If overwriting the full document array or subdoc, make sure to clean up any paths that were modified
      // before re: #15108
      if (this.$__schemaType.$isMongooseDocumentArray || this.$__schemaType.$isSingleNested) {
        cleanModifiedSubpaths(parent, path);
      }
    }

    // Delay calculating full path unless absolutely necessary, because string
    // concatenation is a bottleneck re: #13171
    function fullPath() {
      if (_fullPath) {
        return _fullPath;
      }
      _fullPath = this.$__path + '.' + key;
      return _fullPath;
    }
  }
Overwrites native Map's `set()` function to support setters, `populate()`,
and change tracking. Note that Mongoose maps _only_ support strings and
ObjectIds as keys.

Keys also cannot:
- be named after special properties `prototype`, `constructor`, and `__proto__`
- start with a dollar sign (`$`)
- contain any dots (`.`)

#### Example:

    doc.myMap.set('test', 42); // works
    doc.myMap.set({ obj: 42 }, 42); // Throws "Mongoose maps only support string keys"
    doc.myMap.set(10, 42); // Throws "Mongoose maps only support string keys"
    doc.myMap.set("$test", 42); // Throws "Mongoose maps do not support keys that start with "$", got "$test""

@api public
@method set
@memberOf Map