user2867106
user2867106

Reputation: 1131

JSON stringify in Node JS not serializing array of objects

I am using sails.js (node js framework).

I am trying to JSON.stringify one of the objects, but when I do that it omits one of the fields (rooms array below).

Here is what console.log(object) gives me:

[ { rooms: [ [Object], [Object] ],
    state: '53df76c278999310248072c6',
    name: 'Sydney Center',
    menuItems: null,
    createdAt: Mon Aug 04 2014 23:42:08 GMT+0300 (Jerusalem Summer Time),
    updatedAt: Mon Aug 04 2014 23:42:08 GMT+0300 (Jerusalem Summer Time),
    id: '53dff0205c89c03428a31cee' },
  { rooms: [ [Object], [Object], [Object] ],
    state: '53df76c278999310248072c6',
    createdAt: Mon Aug 04 2014 23:43:21 GMT+0300 (Jerusalem Summer Time),
    menuItems: null,
    name: 'Batata Center',
    updatedAt: Mon Aug 04 2014 23:51:11 GMT+0300 (Jerusalem Summer Time),
    id: '53dff06a5c89c03428a31cf3' } ]

JSON output (rooms are not printed):

    [
      {
        "state": "53df76c278999310248072c6",
        "name": "Sydney Center",
        "menuItems": null,
        "createdAt": "2014-08-04T20:42:08.885Z",
        "updatedAt": "2014-08-04T20:42:08.885Z",
        "id": "53dff0205c89c03428a31cee"
      },
      {
        "state": "53df76c278999310248072c6",
        "createdAt": "2014-08-04T20:43:21.999Z",
        "menuItems": null,
        "name": "Batata Center",
        "updatedAt": "2014-08-04T20:51:11.740Z",
        "id": "53dff06a5c89c03428a31cf3"
      }
    ]

What might be the problem? The rooms data seems to be fine.

For the complete function (SailsJS):

getCentersData: function(req, res) {

    sails.log.info('Teacher.getCentersData: ', req.user.id);
    var userId = req.user.id;

    async.auto({

        teacher: function(cb) {
            Teacher.findOne({ user: userId }).populate('centers').exec(cb);
        },

        centers: [
            'teacher', function(cb, results) {
                var allCentersIds = _.pluck(results.teacher.centers, 'id');
                Center.findById(allCentersIds).populate('rooms').exec(cb);
            }
        ],

        rooms: [
            'centers', function(cb, results) {
                var allRoomIds = [];
                _.each(results.centers, function(center) {
                    allRoomIds = _.union(allRoomIds, _.pluck(center.rooms, 'id'));
                });
                Room.findById(allRoomIds).populate('children').exec(cb);
            }
        ],

        children: [
            'rooms', function(cb, results) {
                var allChildrenIds = [];
                _.each(results.rooms, function (room) {
                    allChildrenIds = _.union(allChildrenIds, _.pluck(room.children, 'id'));
                });
                Child.findById(allChildrenIds).populate('parents').exec(cb);
            }
        ],

        parentUsers: ['children', function(cb, results) {
            var allParentIds = [];
            _.each(results.children, function (child) {
                allParentIds = _.union(allParentIds, _.pluck(child.parents, 'id'));
            });
            Parent.findById(allParentIds).populate('user').exec(cb);
        }],

        map: ['parentUsers', function (cb, results) {

            // map children to parents
            var parentsMapper = _.indexBy(results.parentUsers, 'id');
            var childrenMappedToParents = _.map(results.children, function (child) {
                var _child = child.toObject();
                _child.parents = _.map(child.parents, function (parent) {
                    return parentsMapper[parent.id];
                });
                return _child;
            });
            var childrenMapper = _.indexBy(childrenMappedToParents, 'id');

            // map rooms to children
            var roomsMappedToChildren = _.map(results.rooms, function (room) {
                var _room = room.toObject();
                _room.children = _.map(room.children, function (child) {
                    return childrenMapper[child.id];
                });
                return _room;
            });
            var roomsMapper = _.indexBy(roomsMappedToChildren, 'id');

            // map center to rooms
            var centersMappedToRooms = _.map(results.centers, function (center) {
                var _center = center.toObject();
                _center.rooms = _.map(center.rooms, function (room) {
                    return roomsMapper[room.id];
                });
                return _center;
            });

            sails.log.info('centersMappedToRooms',centersMappedToRooms ); // includes rooms array
            sails.log.info('centersMappedToRooms json: ', JSON.stringify(centersMappedToRooms)); // does not include rooms array

            return cb(null, centersMappedToRooms);

        }]
    }, function(err, results) {
        if (err) {
            return res.serverError(err);
        }

        // added prints
        sails.log.info("results.map: ", results.map); 
        sails.log.info("JSON.stringify(results.map): ", JSON.stringify(results.map)); // same same, does not print the rooms array

        return res.json(results.map);
    });

},

EDITED

Schema:

schema: true,

attributes: {

    name: {
        type: 'string',
        required: true,
        minLength: 5
    },

    // Many-To-Many association with Teacher model
    teachers: {
        collection: 'teacher',
        via: 'centers'
    },

    // One-To-Many association with State model
    state: {
        model: 'state'
    },

    // One-To-Many association with Room model
    rooms: {
        collection: 'room',
        via: 'center'
    },

    // One-To-One association with Principal model
    principal: {
        model: 'principal'
    },

    menuItems: {
        type: 'array',
        defaultsTo: null
    }
},

Upvotes: 3

Views: 5473

Answers (1)

particlebanana
particlebanana

Reputation: 2416

Because Waterline queries return models, not plain javascript objects, they have additional properties and functions on them. One of these is an overridden toJSON() function which removes attributes that have not been populated. What seems to be happening here is that you are attaching objects to a parent model which doesn't know it has children that have been populated so it strips off the values.

The reasoning behind this is so that if you query for all Users and don't populate Rooms you don't get an incorrect result showing an empty rooms array.

I'm not sure what all you are manipulating here but the reason it works if you _.cloneDeep is because it removes the custom toJSON field. This is the recommend strategy when you are mutating a parent object from a query like this.

Upvotes: 9

Related Questions