Spread syntax returns unexpected object
Asked Answered
E

3

19

I am using node and i have used .

babel-node

    "start": "nodemon --exec babel-node --presets es2015 index.js"

My spread syntax is not working as expected. Here is my code.

   export const login = async (parentValue, { email, password }) => {
  try {
    const user = await User.findOne({
      email
    });
    console.log(user);

    if (!user.authenticateUser(password)) {
      throw new Error('Wrong password');
    }
    const dummyObject = {
      ...user
    };
    console.log({ dummyObject });
    return { ...user };
  } catch (e) {
    console.log(e);
    throw new Error(e.message);
  }
};

The line where i have used console.log(user), it works fine. It returns { id: xxx, name: xxxx }

and I am getting unexpected data on console.log(dummyObject); here is what i get.

{ jojo: 
{ '$__': 
      InternalCache {
        strictMode: true,
        selected: {},
        shardval: undefined,
        saveError: undefined,
        validationError: undefined,
        adhocPaths: undefined,
        removing: undefined,
        inserting: undefined,
        saving: undefined,
        version: undefined,
        getters: {},
        _id: 5c798295f53323b34cabf1ca,
        populate: undefined,
        populated: undefined,
        wasPopulated: false,
        scope: undefined,
        activePaths: [Object],
        pathsToScopes: {},
        cachedRequired: {},
        session: undefined,
        ownerDocument: undefined,
        fullPath: undefined,
        emitter: [Object],
        '$options': [Object] },
     isNew: false,
     errors: undefined,
     _doc: 
      { _id: 5c798295f53323b34cabf1ca,
        fullName: 'sarmad',
        password: '$2a$10$c.XDX75ORXYA4V/hUXWh.usVf2TibmKfY.Zpu3cpTssFaYvsGyhte',
        email: '[email protected]',
        createdAt: 2019-03-01T19:05:57.454Z,
        updatedAt: 2019-03-01T19:05:57.454Z,
        __v: 0 },
     '$init': true } }

Am I doing something wrong? Technically it should return the user object NOTE: I don't want to use Object.assign

Exertion answered 1/3, 2019 at 19:38 Comment(2)
No, it looks perfectly fine, in so far that this is a copy of your data object, which is not strictly just the user object you are expecting ;) I am going to guess here that the spread operator takes all, where as the original user object just shows only a few properties to be enumeratedCarrero
I have used the same approach while creating a user. it works there. Maybe the return data for user.create and user.find are different?Exertion
H
42

Looks like you're using mongoose, and it looks like you're getting the mongoose object properties by using the spread operator. You need to convert to JSON to get rid of these.

Try: const dummyObject = { ...user.toJSON() };

You can also: const dummyObject = { ...user.toObject() };

^ This might be the preferred way

Another solution is to only request a plain object when making your query. For instance:

Schema.findOne(query).lean()

This will return a plain object instead of a mongoose object.

Hanky answered 1/3, 2019 at 19:43 Comment(5)
Perfect :), May i know the reason why this happened? it didn't happen while creating a user.Exertion
I'm not too clear on Mongoose internals, but something tells me that when you spread user you are also spreading other hidden properties. Your user object has these properties, but they are most likely hidden at console.log or perhaps mongoose overwrites how console.log enacts on a mongoose object.Hanky
Your solution worked. Now there is just a problem. It returned _id and my graphql is not able to recognise it now, anyways. thats something else.Exertion
That is because it is probably not a ObjectId type any more and is now a string. You could remedy this by getting the ObjectId type: const { ObjectId } = mongoose.Types; and then wrapping the _id: const dataToGraphQL = ObjectId(dummyObject._id) or however you are trying to get it.Hanky
Thanks :) that was a very helpful infoExertion
I
1

Yes, your expected result is right you will get that extra field like "$__",isNew: false, These are the default fields that you will get during a db call in Mongoose.

*note: when you console the result you may see is a plain object but it is a Mongoose Document instead of a plain object.

If you want to return a plain document in response then use .lean(). When you use .lean() in a Mongoose query, it tells Mongoose to return a plain JavaScript object instead of a Mongoose Document. for example:

const user = await User.findOne({
      email
    }).lean();

official doc on .lean()

or you can also do user.toJSON() to get a simple plain doc without any extra property.

Iou answered 24/9, 2023 at 14:49 Comment(0)
C
0

You get different logs because mongoose uses custom inspection function

Try this in node:

const obj = {
  [Symbol.for('nodejs.util.inspect.custom')]() {
    return "totally not an object";
  }
}

console.log(obj); // "totally not an object"

Since mongoose inspect is defined on object's prototype it isn't copied when you use ... since spread only copies object's own properties.

class Obj {
  [Symbol.for('nodejs.util.inspect.custom')]() {
    return "totally not an object";
  }
}

const obj = new Obj();
const obj2 = { ...obj };


console.log(obj); // "totally not an object"
console.log(obj2); // {}

You can fix it by setting a prototype to the copied object:

Reflect.setPrototypeOf(obj2, Reflect.getPrototypeOf(obj))

but since you are dealing with custom objects an object spread shouldn't be really used. Spread is safe only for POJO. Otherwise you may get into troubles easily (with hidden props, getters, setters and prototype hell)

https://repl.it/repls/ToughModestInstructionset

https://github.com/Automattic/mongoose/blob/master/lib/document.js#L2853:L2869

https://nodejs.org/api/all.html#util_util_inspect_custom

Clang answered 1/3, 2019 at 21:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.