Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Converting all ObjectIds into string by using Document.toObject() method? #2790

Closed
kitlee opened this issue Mar 25, 2015 · 25 comments
Closed
Labels
help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary

Comments

@kitlee
Copy link

kitlee commented Mar 25, 2015

As I found that the _id in the document is still an ObjectId after toObject()

Is there a way to achieve this?

@Mickael-van-der-Beek
Copy link
Contributor

I've got the same bug. Both toJSON() and toObject() don't stringify ObjectId instances.

@vkarpov15
Copy link
Collaborator

This is by design right now, because ObjectIds technically aren't strings. Off the top of my head the only way I can think of would be to JSON.parse(JSON.stringify(doc)); What's your use case for stringifying all object ids?

@vkarpov15 vkarpov15 added the help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary label Jun 1, 2015
@tiant167
Copy link

so, function toJSON() isn't convert the object to JSON string ? The _id and date isn't a string but an object ?

@tiant167
Copy link

by the way, what is the difference between toObject() and toJSON()?
thx!

@vkarpov15
Copy link
Collaborator

Yeah date should still be a date, not a string, after toJSON() + toObject().

toJSON() and toObject() are essentially identical, the only difference is that when you do JSON.stringify(doc) JavaScript looks for a toJSON() function on doc and uses it to convert the object. So the reason for toJSON() is syntactic sugar for JSON.stringify().

@linusbrolin
Copy link

@vkarpov15 Sorry for commenting on a closed issue, but it seemed the most relevant place to post, since it's directly related to this issue.

I'm trying to deep-compare two mongoose docs to get a diff object of the changes between them.
ObjectID's however, cannot be compared directly it seems, because they will always end up with a diff, even if they contain the exact same id's.

The only way I've been able to do it so far, is to do this:

var objectdiff = require('object-deep-diff');

var diffresult = objectdiff(
  JSON.parse(JSON.stringify(doc1.toObject())),
  JSON.parse(JSON.stringify(doc2.toObject()))
);

That seems like an expensive way of getting a diff between objects.
Do you know of a better way to reliably compare mongoose docs?

@vkarpov15
Copy link
Collaborator

Not sure how deep diff works but it might be running into some issues with mongodb ObjectIds. Try converting _id and other objectid fields to strings. Or just use lodash's deep equality check, I think that one works with ObjectIds

@linusbrolin
Copy link

@vkarpov15 deep diff uses lodash for the equality check, so unfortunately that won't work:
https://github.com/rbs392/object-deep-diff/blob/master/index.js

Converting all ObjectId's to strings would work, but since the objects can contain arrays and/or other nested objects, that becomes quite an expensive task to do aswell.. :/

@vkarpov15
Copy link
Collaborator

So here's another question, are you using deep-diff on 2 mongoose documents or 2 documents that are the result of toObject()? The latter should work, the former not so much.

@linusbrolin
Copy link

@vkarpov15 it's usually 1 mongoose doc (converted to a javscript object with .toObject) and one regular javascript object.
The reason is that I need to compare data coming from the client, with the data already in the database.
So, the data coming from the client already have the ObjectID's converted, but the data coming from the database haven't.
But since I'm doing this deep-diff check in a mongoose plugin, I need to make absolutely sure that both docs are constructed identically, so that's why I have to run JSON.parse(JSON.stringify(doc.toObject())) on both docs.

@vkarpov15
Copy link
Collaborator

Yeah in that case you're going to need to do JSON.parse(JSON.stringify()), that's the easiest way to convert all the object ids to strings.

@ZacharyRSmith
Copy link

ZacharyRSmith commented Oct 6, 2017

JSON.parse(JSON.stringify()) was too computationally intensive for me, so I had to create this perf-thinking lib.

var transformProps = require('transform-props');
 
function castToString(arg) {
    return String(arg);
}
 
var doc = new MongooseModel({ subDoc: { foo: 'bar' }});
var docObj = doc.toObject();
 
transformProps(docObj, castToString, '_id');
 
console.log(typeof docObj._id); // 'string'
console.log(typeof docObj.subDoc._id); // 'string'

@wi-ski
Copy link

wi-ski commented Apr 8, 2018

This feature of Mongo is really regrettable

@flight9
Copy link

flight9 commented Jul 27, 2018

@ZacharyRSmith Then what's about other props, eg. author_id(ObjectId refers to User Model)/author_ids(array of ObjectIds)?

@ZacharyRSmith
Copy link

@flight9 you can tell transformProps() to look for multiple keys. see this test here: https://github.com/ZacharyRSmith/transform-props/blob/master/index.test.js#L67

i believe you can get what you want with this:

function myToString(val) {
  if (Array.isArray(val)) {
    return val.map(item => String(item));
  }
  return String(val);
}

transformProps(obj, myToString, ['author_id', 'author_ids']);

@flight9
Copy link

flight9 commented Jul 27, 2018

@ZacharyRSmith Thanks for your solution, It's great!

I think i missed one important thing: I don't konw which fields are ObjectId beforehand, because the tool I'm writing should be applied to all models in our project, which may have several ObjectId fields or have none, and its field names are indeterminate.
So after several hours' trying, I have my solution:

const ObjectID = require('mongodb').ObjectID;

/**
 * Convert ObjectId field values inside an object to String type values
 * (includes array of ObjectIds and nested ObjectId fields)
 * @param obj Object - The Object to be converted.
 * @return none
 */
function objectIdtoString(obj) {
  for(let k in obj) {
    let v = obj[k];
    if(typeof v == 'object') {
      if(k.endsWith('_id') && v instanceof ObjectID) {
        obj[k] = v.toString();
      }
      else if(
        k.endsWith('_ids') && Array.isArray(v) &&
        v.length > 0 && v[0] instanceof ObjectID
      ) {
        let vs = [];
        for(let iv of v) {
          vs.push(iv.toString());
        }
        obj[k] = vs;
      }
      else {
        objectIdtoString(v);
      }
    }
  }
}

// Call example
objectIdtoString(obj):

@Billy-
Copy link

Billy- commented Aug 30, 2018

@vkarpov15

Yeah in that case you're going to need to do JSON.parse(JSON.stringify()), that's the easiest way to convert all the object ids to strings.

https://medium.com/ft-product-technology/this-one-line-of-javascript-made-ft-com-10-times-slower-5afb02bfd93f

tldr; JSON.parse(JSON.stringify()) is a synchronous, blocking task, and can cause serious bottlenecks. This should not be the accepted/suggested solution for this issue...

@vkarpov15
Copy link
Collaborator

You should also be able to do mongoose.Types.ObjectId.prototype.toJSON = function() { return this.toString(); . But even that article says, JSON.parse(JSON.stringify) is one of the faster ways of doing deep clone, their problem was that they were unnecessarily deep cloning repeatedly

@gitowiec
Copy link

gitowiec commented Jul 8, 2019

So, here is 2019.
Do we yet have nice and clean and obvious way to get hex string from every ObjectId instance on object?

@vkarpov15
Copy link
Collaborator

@gitowiec

mongoose.ObjectId.get(v => v.toString());

See: http://thecodebarbarian.com/whats-new-in-mongoose-54-global-schematype-configuration.html#schematype-getters

@nhitchins
Copy link

nhitchins commented Nov 21, 2019

@vkarpov15 looks like this is not a solution when using 'lean'.
So if the desired result is to return a plain json object with string objectIds it seems the most efficient way to achieve this is to still use JSON.parse(JSON.stringify()) with the lean option.

Or possibly a lean plugin: https://www.npmjs.com/package/mongoose-lean-objectid-string

@vkarpov15
Copy link
Collaborator

@nhitchins correct, you'd have to use a plugin like mongoose-lean-getters.

@pwrnrd
Copy link

pwrnrd commented Dec 7, 2019

@vkarpov15 is this also supposed to work with a discriminator schema? I have a base schema from which I create variants. I tried setting getters on the base schema. I use lean() and used mongoose-lean-getters but I couldn't get this to work.

@vkarpov15
Copy link
Collaborator

@pwrnrd can you please open a new issue and follow the issue template? It should work fine with a discriminator schema.

@caramboleyo
Copy link

caramboleyo commented Sep 12, 2022

Google took me here with “javascript recursive convert of ObjectId to string”
I needed it to put a result into a worker where the ObjectIDs ended up like { _bsontype: "ObjectID" }, which was pretty useless.
If you came this way too, thats what i went with:

function recurisveObjectIdStringifyer(o) {
	if (typeof o == 'object' && o != null) {
		if (o instanceof Bson.ObjectId) {
			o = o.toString();
		} else if (Array.isArray(o)) {
			for (const k in o) {
				o[k] = recurisveObjectIdStringifyer(o[k]);
			}
		} else {
			for (const k of Object.keys(o)) {
				o[k] = recurisveObjectIdStringifyer(o[k]);
			}
		}
	}
	return o;
};

TEST

console.log(recurisveObjectIdStringifyer({
	one: 1,
	two: null,
	three: 'string',
	four: new Bson.ObjectId('631f0cddb2bc5192fae9da97'),
	five: {
		one: 1,
		two: null,
		three: 'string',
		four: new Bson.ObjectId('631f0cddb2bc5192fae9da97')
	},
	six: [{
		one: 1,
		two: null,
		three: 'string',
		four: new Bson.ObjectId('631f0cddb2bc5192fae9da97'),
		five: {
			one: 1,
			two: null,
			three: 'string',
			four: new Bson.ObjectId('631f0cddb2bc5192fae9da97')
		}
	}, {
		one: 1,
		two: null,
		three: 'string',
		four: new Bson.ObjectId('631f0cddb2bc5192fae9da97'),
		five: {
			one: 1,
			two: null,
			three: 'string',
			four: new Bson.ObjectId('631f0cddb2bc5192fae9da97')
		}
	}]
}));

@Automattic Automattic locked and limited conversation to collaborators Sep 30, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary
Projects
None yet
Development

No branches or pull requests