Nested arrays in object json to csv - json

Im using 'json-csv' library to create a csv from a users arrays with nested objects and arrays.
var users = [
{
subscriptions: [
{
package : {
name: 'Grammar'
},
state: 'EXPIRED',
timerange: {
period : 5550
},
transaction:{
amount: 10000
}
},
{
package : {
name: 'GK'
},
state: 'ACTIVE',
timerange: {
period : 30
},
transaction:{
amount: 10340
}
},
],
account:{
balance: 200
},
name: "Johhy Moe",
email: null,
user_id: "123456789",
username: null,
user_type: "facebook",
id: 3,
createdAt: "2016-07-11T08:02:40.000Z",
updatedAt: "2016-07-11T08:02:40.000Z",
},
{
subscriptions: [
{
package : {
name: 'GK'
},
state: 'EXPIRED',
timerange: {
period : 42
},
transaction:{
amount: 5252
}
},
{
package : {
name: 'MATH'
},
state: 'ACTIVE',
timerange: {
period : 25
},
transaction:{
amount: 200
}
}
],
account:{
balance: 1500
},
name: "John Doe",
email: null,
user_id: "123456789",
username: null,
user_type: "facebook",
id: 7,
createdAt: "2016-07-29T06:44:18.000Z",
updatedAt: "2016-07-29T06:44:18.000Z"
},
]
Now i want the generated csv to be like this
USERID,NAME,FBID,ACCOUNT,SUBSCRIPTION,PRICE,STATE,TIMEPERIOD
3,Johhy Moe,123456789,200,Grammar,10000,EXPIRED,5550
3,Johhy Moe,123456789,200,GK,10340,ACTIVE,30
7,John Doe,123456789,1500,GK,5252,EXPIRED,30
7,John Doe,123456789,1500,MATH,200,ACTIVE,25
As you see if there are two objects inside subscription array for each user, i want to repeat that user again but with different subscription data.
I've thought of using the library because my users array can go up to thousands of users with hundreds of subscription.
And i'm at a loss to what i should do.
my Code:
var options= {
fields : [
{
name : 'id',
label : 'USERID'
},
{
name : 'name',
label : 'Name'
},
{
name : 'user_id',
label : 'FBID'
},
{
name : 'account.balance',
label : 'ACCOUNT'
},
{
name: '',
label: 'Subscription'
}
]
}
var source = es.readArray(users)
source
.pipe(jsoncsv.csv(options))
.pipe(res)
I dont want to use a library also. So if someone could provide me with a resource to make my own csv file with strings and also using streams , that would be great. Thanks!!

This will solve your problem. Now you just have to change console.log to fs and write to your file.
var json2csv = function (json, listKeys) {
var str = "";
var prefix = "";
for (var i = 0; i < listKeys.length; i++) {
str += prefix + json[listKeys[i]];
prefix = ",";
}
return str;
};
var async = require('async');
var csvData = ['USERID,NAME,FBID,ACCOUNT,SUBSCRIPTION,PRICE,STATE,TIMEPERIOD'];
async.each(users, function (user, callback) {
var csvRow1 = {
USERID: user.id,
NAME: user.name,
FBID: user.user_id,
ACCOUNT: user.account.balance
};
async.each(user.subscriptions, function (subscription, callback) {
var csvRow2 = JSON.parse(JSON.stringify(csvRow1));
csvRow2.SUBSCRIPTION = subscription.package.name;
csvRow2.PRICE = subscription.transaction.amount;
csvRow2.STATE = subscription.state;
csvRow2.TIMEPERIOD = subscription.timerange.period;
csvData.push(json2csv(csvRow2, ['USERID', 'NAME', 'FBID', 'ACCOUNT', 'SUBSCRIPTION', 'PRICE', 'STATE', 'TIMEPERIOD']));
callback(null);
}, function (err) {
callback(err);
});
}, function (err) {
if (err) {
// return err;
} else {
// return csvData;
}
});

Related

I'm trying to join 3 tables in node.js using sequelize but having trouble. What is the problem with my code?

Newer to Node.js any advice would be appreciated! Trying to join 3 tables with a common key but getting the error...
react_devtools_backend.js:4026 [GraphQL error]: Message: Unknown column 'google_responsive_descriptions.googleTextAdId' in 'on clause', Location: [object Object], Path: listGoogleTextAds
googleResponsiveAds.js file
module.exports = (sequelize, DataTypes) => {
const googleResponsiveAds = sequelize.define(
"google_responsive_headlines",
{
responsive_headlines: { type: DataTypes.STRING },
responsive_path_1: { type: DataTypes.STRING },
responsive_path_2: { type: DataTypes.STRING }
},
{
timestamps: false,
tableName: "google_responsive_headlines"
}
);
//googleResponsiveAds.associate = () => {};
googleResponsiveAds.associate = function (models) {
googleResponsiveAds.belongsTo(models.google_text_ads, { foreignKey: "ad_id" });
}
return googleResponsiveAds;
};
googleResponsiveDescriptionsAds.js file
module.exports = (sequelize, DataTypes) => {
const googleResponsiveDescriptionsAds = sequelize.define(
"google_responsive_descriptions",
{
responsive_descriptions: { type: DataTypes.STRING }
},
{
timestamps: false,
tableName: "google_responsive_descriptions"
}
);
//googleResponsiveDescriptionsAds.associate = () => {};
googleResponsiveDescriptionsAds.associate = function (models) {
googleResponsiveDescriptionsAds.belongsTo(models.google_text_ads, { foreignKey: "ad_id" });
}
return googleResponsiveDescriptionsAds;
};
googleTextAds.js file
module.exports = (sequelize, DataTypes) => {
const googleTextAds = sequelize.define(
"google_text_ads",
{
headline_pt_1: { type: DataTypes.STRING },
headline_pt_2: { type: DataTypes.STRING },
headline_pt_3: { type: DataTypes.STRING },
final_url: { type: DataTypes.STRING },
display_url: { type: DataTypes.STRING },
status: { type: DataTypes.STRING },
type: { type: DataTypes.STRING },
description1: { type: DataTypes.STRING },
description2: { type: DataTypes.STRING },
path1: { type: DataTypes.STRING },
path2: { type: DataTypes.STRING },
ad_id: { type: DataTypes.INTEGER }
},
{
timestamps: false,
tableName: "google_text_ads"
}
);
//googleTextAds.associate = () => {};
googleTextAds.associate = function (models) {
googleTextAds.hasMany(models.google_responsive_headlines, { sourceKey: 'ad_id' })
googleTextAds.hasMany(models.google_responsive_descriptions, { sourceKey: 'ad_id' });
};
return googleTextAds;
};
Here is the section of the queries/google.js
{
key: "listGoogleTextAds",
prototype:
"(customer_id: Int, start_date: String, end_date: String): [GoogleTextAds]",
run: async args => {
const allIds = await google_text_ads
.findAll({
attributes: [
"ad_id",
"date",
"impressions",
"clicks",
"cost"
],
include: [
{
model: google_responsive_descriptions,
as: 'google_responsive_descriptions',
required: true,
attributes: [
"ad_id",
"responsive_descriptions"
],
},
{
model: google_responsive_headlines,
as: 'google_responsive_headlines',
attributes: [
"ad_id",
"responsive_headlines",
"responsive_path_1",
"responsive_path_2"
]
}
],
where: {
customer_id: args.customer_id,
date: {
[Op.gte]: args.start_date,
[Op.lte]: args.end_date
},
status: {
[Op.in]: ["ENABLED"]
},
type: {
[Op.in]: ["EXPANDED_TEXT_AD", "RESPONSIVE_SEARCH_AD"]
}
}
})
}
}
EDIT: Here's my query
export const LIST_GOOGLE_TEXT_ADS = gql`
query listGoogleTextAds(
$customer_id: Int!
$start_date: String!
$end_date: String!
) {
listGoogleTextAds(
customer_id: $customer_id
start_date: $start_date
end_date: $end_date
) {
ad_id
type
headline_pt_1
headline_pt_2
headline_pt_3
description1
description2
path1
path2
status
final_url
display_url
impressions
clicks
cost
}
}
`;
and my models...
type GoogleTextAds {
ad_id: Int
type: String
headline_pt_1: String
headline_pt_2: String
headline_pt_3: String
description1: String
description2: String
path1: String
path2: String
final_url: String
display_url: String
status: String
impressions: Int
clicks: Int
cost: Float
}
type GoogleResponsiveAds{
ad_id: Int
responsive_headlines: String
responsive_path_1: String
responsive_path_2: String
}
type GoogleResponsiveDescriptionsAds{
ad_id: Int
responsive_descriptions: String
}
here is the query I am trying to replicate...
SELECT distinct google_responsive_headlines.responsive_headlines,
google_responsive_headlines.responsive_path_1,
google_responsive_headlines.responsive_path_2,
google_responsive_descriptions.responsive_descriptions,
google_text_ads.date,
google_text_ads.clicks,
google_text_ads.cost,
google_text_ads.impressions,
google_text_ads.ad_id,
google_text_ads.status,
google_text_ads.final_url,
google_text_ads.create_time
FROM irene_db.google_text_ads
inner JOIN irene_db.google_responsive_headlines ON google_responsive_headlines.ad_id = google_text_ads.ad_id
inner JOIN irene_db.google_responsive_descriptions ON google_responsive_descriptions.ad_id = google_text_ads.ad_id
where google_text_ads.customer_id = 144 and google_text_ads.date = '2022-06-09' and
google_responsive_headlines.customer_id = 144 and google_responsive_headlines.date = '2022-06-09' and
google_responsive_descriptions.customer_id = 144 and google_responsive_descriptions.date = '2022-06-09';
EDIT2: Where associate gets called...
const fs = require("fs");
const path = require("path");
const Sequelize = require("sequelize");
const dotenv = require("dotenv");
dotenv.config();
const basename = path.basename(module.filename);
const db = {};
let sequelize;
const { DB_HOST, DB_USER, DB_PASS, DB_NAME } = process.env;
sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASS, {
dialect: "mysql",
host: DB_HOST
});
fs.readdirSync(__dirname)
.filter(
file =>
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
)
.forEach(file => {
const model = sequelize.import(path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
EDIT 3:
After boc4life's code suggestions I'm now at least getting graphql to attempt the query. But it's using the wrong field name in the join on section. Here's the query it built...
SELECT
`google_text_ads`.`id`,
`google_text_ads`.`ad_id`,
`google_text_ads`.`date`,
`google_text_ads`.`impressions`,
`google_text_ads`.`clicks`,
`google_text_ads`.`cost`,
`google_responsive_descriptions`.`id` AS `google_responsive_descriptions.id`,
`google_responsive_descriptions`.`responsive_descriptions` AS `google_responsive_descriptions.responsive_descriptions`,
`google_responsive_headlines`.`id` AS `google_responsive_headlines.id`,
`google_responsive_headlines`.`responsive_headlines` AS `google_responsive_headlines.responsive_headlines`,
`google_responsive_headlines`.`responsive_path_1` AS `google_responsive_headlines.responsive_path_1`,
`google_responsive_headlines`.`responsive_path_2` AS `google_responsive_headlines.responsive_path_2`
FROM
`google_text_ads` AS `google_text_ads`
INNER JOIN
`google_responsive_descriptions` AS `google_responsive_descriptions` ON `google_text_ads`.`ad_id` = `google_responsive_descriptions`.`googleTextAdId`
INNER JOIN
`google_responsive_headlines` AS `google_responsive_headlines` ON `google_text_ads`.`ad_id` = `google_responsive_headlines`.`googleTextAdId`
WHERE
`google_text_ads`.`customer_id` = 142
AND (`google_text_ads`.`date` >= '2022-05-17'
AND `google_text_ads`.`date` <= '2022-06-17')
AND `google_text_ads`.`status` IN ('ENABLED')
AND `google_text_ads`.`type` IN ('EXPANDED_TEXT_AD' , 'RESPONSIVE_SEARCH_AD');
Try using the tableName defined while defining the table structure to access the model while creating associations.
Example: instead of
models.googleTextAds
do
models.google_text_ads
What jumps out to me is all of those nested includes. Since you are joining the Headlines and Descriptions tables on the Text_Ads table, I believe all you should need here is the one include array containing two objects, one for Headlines and one for Descriptions.
Currently you have Headlines nested under Descriptions, which won't work because Descriptions does not have an association with Headlines directly defined. You also have an include of Text_Ads nested inside of Descriptions, which should WORK, but should be unnecessary since that is the model you are calling findAll() on. You can bring the Text_Ads attributes you are querying for out into that parent object as a sibling of include. Something like this looks like a good starting point for getting the query cleaned up. I have also removed a bunch of the unnecessary sequelize.col() that you had in your initial post.
const allIds = await google_text_ads
.findAll({
attributes: [
"ad_id",
[sequelize.fn("max", sequelize.col("date")), "date"],
[sequelize.fn("sum", sequelize.col("impressions")), "impressions"],
[sequelize.fn("sum", sequelize.col("clicks")), "clicks"],
[sequelize.fn("sum", sequelize.col("cost")), "cost"]
],
include: [
{
model: Models.google_responsive_descriptions,
as: 'googleResponsiveDescriptionsAds',
required: true,
attributes: [
"ad_id",
"responsive_descriptions"
],
},
{
model: Models.google_responsive_headlines,
as: 'googleResponsiveAds',
attributes: [
"ad_id",
"responsive_headlines",
"responsive_path_1",
"responsive_path_2"
]
}
],
where: {
customer_id: args.customer_id,
date: {
[Op.gte]: args.start_date,
[Op.lte]: args.end_date
},
status: {
[Op.in]: ["ENABLED"]
},
type: {
[Op.in]: ["EXPANDED_TEXT_AD", "RESPONSIVE_SEARCH_AD"]
}
}
})

Append data to an object upon Sequelize Model Fetch

I have a service function to get users from my MySQL database using Sequelize ORM and i would to like to append fullName to the Users.
const filterUsers = async ([...users]:IUsers[]) => {
let searchAttributes = {};
if (users) { searchAttributes = { [Op.or]: users }; }
const filteredUsers = await User.findAll({
raw: true,
nest: true,
where: { ...searchAttributes },
include: [{
model: Club,
as: 'homeClub',
}, {
model: Club,
as: 'awayClub',
}] });
return filteredUsers as unknown as IUsersWithTeams[];
};
FilteredUsers response:
filteredUsers =
[
{
id: 1
name: 'John',
LastName: 'Mayer',
homeClub: 'Barcelona',
awayClub: 'Real Madrid',
},
{
id: 2,
name: 'Adam',
LastName: 'Smith',
homeClub: 'PSG',
awayClub: 'Milan',
},
]
What i would like to receive:
const expectedUserResponse = [
{
id: 1
name: 'John',
LastName: 'Mayer',
FullName: 'John Mayer',
homeClub: 'Barcelona',
awayClub: 'Real Madrid',
},
{
id: 2,
name: 'Adam',
LastName: 'Smith',
FullName: 'Adam Smith',
homeClub: 'PSG',
awayClub: 'Milan',
},
]
How can i do that ? Thank you very much if you can help me ;)
You need to use a virtual field, see documentation and my other answer to the similar question. It seems the official documentation has the example that perfectly fits your request:
const { DataTypes } = require('#sequelize/core');
const User = sequelize.define('user', {
firstName: DataTypes.TEXT,
lastName: DataTypes.TEXT,
fullName: {
type: DataTypes.VIRTUAL,
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
throw new Error('Do not try to set the `fullName` value!');
}
}
});

GraphQL - operating elements of array

I would like to display some information about members, but I don't know how to resolve array of field 'time'. This is array, because it shows their login time. What should I do?
I used GraphQLString, but I am aware of this bad solution.
So I'm getting an error:
"message": "String cannot represent value: [\"12:08\"]",
Here is schema.js
const axios = require("axios");
const {
GraphQLObjectType,
GraphQLString,
GraphQLList,
GraphQLSchema
} = require("graphql");
const memberType = new GraphQLObjectType({
name: "Member",
fields: () => ({
nick: {
type: GraphQLString
},
name_and_surname: {
type: GraphQLString
},
time: {
type: GraphQLString
}
})
});
//Root Query
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
users: {
type: new GraphQLList(memberType),
description: "List of members",
resolve(parent, args) {
return axios
.get("http://25.98.140.121:5000/data")
.then(res => res.data);
}
}
}
})
module.exports = new GraphQLSchema({
query: RootQuery
});
And here is JSON
[
{
"time": [
"12:08"
],
"nick": "Cogi12",
"name_and_surname: "John Steps"
},
{
"time": [
"12:16"
],
"nick": "haris22",
"name_and_surname": "Kenny Jobs"
},
{
"time": [
"12:07",
"12:08",
"12:17",
"12:19",
"12:45",
"13:25"
],
"nick": "Wonski",
"name_and_surname": "Mathew Oxford"
}
]
you can use GraphQLList along with GraphQLString for time type like this,
const memberType = new GraphQLObjectType({
name: "Member",
fields: () => ({
nick: {
type: GraphQLString
},
name_and_surname: {
type: GraphQLString
},
time: {
type: new GraphQLList(GraphQLString)
}
})
});

Getting json object data with react

I am attempting to pull data out of json like this, which is imported as "values"
{
"content": {
"person": [
{
"name": "Test"
"age" : "24:
}
]
}
}
I am using .map like below but getting the error .default.map is not a function I believe it is because i have objects not arrays, i've tried a bunch of stuff including object.keys but i'm getting errors all over the place, any direction would be appreciated.
import values from './sample.json'
const vals = values.map((myval, index) => {
const items = person.items.map((item, i) => {
return (
<div>{item.name}</div>
)
})
return (
<div>{items}</div>
)
})
I think your data and code have some errors. But after fixing those and also changing the name from 'person' to 'people' if that's what you are after, here's the code that does what you are trying to do:
var data = {
content: {
people: [
{
name: "Test",
age: 24,
},
{
name: "Foo",
age: 25,
},
],
},
};
var App = React.createClass({
render: function () {
var people = data.content.people.map(function (person) {
return <div>{person.name}</div>;
});
return <div>{people}</div>;
},
});
ReactDOM.render(<App />, document.getElementById("app"));
And here's the JSBin for that: https://jsbin.com/coyalec/2/edit?html,js,output
Update: I'm updating the answer with more detailed example. It now deals with data more generically, like it doesn't assume what are the entries of 'contents' and such, but it knows that each type like 'people' or 'pets' are an array.
var data = {
content: {
people: [
{
name: "Test",
age: 24,
},
{
name: "Foo",
age: 25,
},
],
pets: [
{
name: "Sweety",
age: 3,
},
{
name: "Kitty",
age: 5,
},
],
},
};
var App = React.createClass({
render: function () {
// Get the keys in data.content. This will return ['people', 'pets']
var contentKeys = Object.keys(data.content);
// Now start iterating through these keys and use those keys to
// retrieve the underlying arrays and then extract the name field
var allNames = contentKeys.map((t) =>
data.content[t].map((e) => <div>{e.name}</div>)
);
return <div>{allNames}</div>;
},
});
ReactDOM.render(<App />, document.getElementById("app"));
And here's the latest JSBin: https://jsbin.com/coyalec/4/edit?html,js,output

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
}
},
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.