accessing properties of nested objects with Lodash - json

Lodash's documentation for map() contains an example like this:
var users = [{'user': 'barney'}, {'user': 'fred'}];
_.map(users, 'user'); // => ['barney', 'fred']
I have data similar to that, but nested. Theirs is an array of objects, but I have an array of objects, each of which contains an array of objects, too. Expanding on the Lodash example, my data is like this:
var users = [
{'mapping': [{'user': 'barney'}, {'user': 'fred'}]},
{'mapping': [{'user': 'sherlock'}, {'user': 'watson'}]},
];
I'd like to get back all four of those names. (An array of arrays is acceptable.)
I tried a number of ways to do this. You can see my attempts at: https://runkit.com/lsloan0000/lodash-map-nested
Eventually, I found this solution:
// I didn't think it would take this much code
_.map(users, function (value, key, collection) {
return _.map(value.mapping, 'user');
});
I thought Lodash has so many features that I wouldn't need to use a callback function.
Is there a simpler way to accomplish this?
I know I can probably skip the key and collection arguments. Eventually, I plan to have this code return the objects whose mapping contains two specific names. (All objects with mappings for "fred" and "barney" together.) So I've left those arguments there, because I think I will need them for that purpose.

Use _.flatMap() to flatten the nested array into a single array, and then map it to user values:
var users = [
{'mapping': [{'user': 'barney'}, {'user': 'fred'}]},
{'mapping': [{'user': 'sherlock'}, {'user': 'watson'}]},
];
var result = _(users)
.flatMap('mapping')
.map('user')
.value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
And without a lodash chain:
var users = [
{'mapping': [{'user': 'barney'}, {'user': 'fred'}]},
{'mapping': [{'user': 'sherlock'}, {'user': 'watson'}]},
];
var result = _.map(_.flatMap(users, 'mapping'), 'user');
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Related

Remove duplicate object copies from array of objects

I have an array of objects that I get from an API. The property names are dynamic (meaning I don't have an extensive list of all of them). How can I get an array of all distinct objects? The contract specifies that if key is equal value is also equal. I tried to look around but I found nothing quite like this problem.
[ 20:31:28
{
'product-management': 'Product management'
},
{
'product-development': 'Product development'
},
{
'client-work': 'Client work'
},
{
'client-work': 'Client work'
},
{
'product-development': 'Product development'
},
{
'client-work': 'Client work'
},
{
'product-development': 'Product development'
}
]
Spread the array into Object.assign() to merge all objects to a single one. Since all objects properties are unique, this will leave only one key (and value) from the duplicates. Then convert to [key, value] pairs with Object.entries(), and map back to individual objects:
const data = [{"product-management":"Product management"},{"product-development":"Product development"},{"client-work":"Client work"},{"client-work":"Client work"},{"product-development":"Product development"},{"client-work":"Client work"},{"product-development":"Product development"}]
const result = Object.entries(Object.assign({}, ...data))
.map(([k, v]) => ({ [k]: v }))
console.log(result)
Going with #Bergi's suggestion, you can also convert this to a saner API while removing duplicates:
const data = [{"product-management":"Product management"},{"product-development":"Product development"},{"client-work":"Client work"},{"client-work":"Client work"},{"product-development":"Product development"},{"client-work":"Client work"},{"product-development":"Product development"}]
const result = Object.entries(Object.assign({}, ...data))
.map(([key, value]) => ({ key, value }))
console.log(result)

Sequelize raw queries TextRow and getting data out of it

Given this query here,
let output = [];
const sql = `select * from coredb.account LIMIT ${offset},${limit}`;
let data = await sequelize.query(sql, null, {raw: true, type: sequelize.QueryTypes.SELECT});
data.forEach((item) => {
console.log(item['id'], item.id); // <-- output says "undefined, undefined"
});
the data variable is indeed hydrated with the right row data when using console.log to inspect it.
But, when I try to access the individual properties, they only ever come back as undefined. This TextRow object that Sequelize seems to return the result in doesn't seem to want to let me access then explicit rows.
Just curious what i'm missing here, am I missing an option?
I agree, Sequalize raw queries are not intuitive. You don't need the null or raw: true flag. Something like this should work:
let data = await sequelize.query(sql, {type: sequelize.QueryTypes.SELECT});
When I tried this, "data" was an array of two objects, each being the query result. So, the properties can be accessed by using index [0].... e.g.
data[0].forEach((item) => {
console.log(item['id'], item.id); // <-- output says "undefined, undefined"
});
Not yet sure WHY this occurs!
EDIT - it's because .query() should have only two arguments. Changing the call to: sequelize.query(sql, {raw: true, type: sequelize.QueryTypes.SELECT}) resulted in data being a single array (as expected).
Finally I was able to find the solution for it.
You just need to make a new array and push data into it by finding bases on key name like this:
suppose we have data in students object:
let finalArray = new Array();
for (var k in students ) {
finalArray.push(students[k])
}
console.log(finalArray) // Normal JSON array object :)
m.sequelize.query(sql, {
model,
mapToModel: true
})
.then(model => res.status(200).send(model))
.catch(error => res.status(400).send(error.toString())
})

Accessing Data in JSON [Nodejs]

I have some JSON that when converted to an Object it looks like the following:
{
'SOME RANDOM STRING':
{
'Article Headline': 'headline',
'Article Image URL': 'image url',
'Article Published Date': 'date',
'Article URL': 'article url',
'Category': 'mental illness,',
'Location': 'place',
'Source Name': 'source'
}
}
I have it stored in an array called results. How would I be able to access the values within Location as result.location doesn't work.
If its a JSON, you don't have to convert it into an array. You can parse it directly something like below.
var obj = {
'-KzZaDXhWRwdzfKUf5tl':
{ 'Article Headline': 'headline',
'Article Image URL': 'image url',
'Article Published Date': 'date',
'Article URL': 'article url',
'Category': 'mental illness,',
'Location': 'place',
'Source Name': 'source' }
}
console.log(obj['-KzZaDXhWRwdzfKUf5tl'].Location);
The above prints place to the screen.
The path to the Object would be results[0]['RANDOM STRING'].location, however since you don't know the RANDOM STRING then it'd be best to use non referential methods to access the nested object.
Thankfully there are many tools in recent version of NodeJS/Javascript to do just this!
Array.prototype.map(function(item, index, array), context) seems like the function you want! It will create a new array based on the return of the function as applied to each thing in the array.
Then you can change each object by using other tools built onto the object itself like
// array of keys, useful for looking for a specific key
Object.keys(someReallyObtuseObject)
// array of VALUES! Awesome for looking for a specific data type
Object.values(someReallyObtuseObject)
Checking Node Green for Object.values shows it's available in NodeJS 7.10 or greater and for Object.keys shows it is available as far back as 4.8.6!
Don't forget though that these transform the object to an array. After that you can use forEach, filter, map, and many other array methods to access the data!
An Example
Say I have an array from a database called results
const results = [{...},{...},...];
I want to find a result with the identifier I know
// I will either find the result, or receive undefined
let result = results.filter(r => r[key] == identifier)[0];
However in my result the object has a key called "related posts" and it is an object with a key being a unique ID for each related post. I want to access said posts, but I don't know their unique IDs, so I want to convert it to an array to make processing it easier
// This gives me an array of related posts with their ID now nested inside them
let relatedPosts = Object.keys(result['related posts']).map(k => {
let r = result['related posts'][k];
r.id = k;
return r;
});
Now I can go over my related posts easily, and I never had to know the ID of the post. Let's say I want to console.log each post(you would really never want to do this)
relatedPosts.forEach(console.log);
Easy!
Example 2, getting the location from an array of users
Users are defined as an object with keys 'first', 'last', 'location'
const users = [{...},{...},...]
let locations = users.map(user => user.location)

How to set a property as an array i initial state?

In my reducer I set initial state by:
const initialState = fromJS({
results: [],
});
However if I try to print results by
initialState.get('results')
I get an immutable Map.
On the other hand if in my reducer (listening to an action) I set the array via
...
case LOAD_SUCCESS:
return state
.set('results', []);
...
the array will be an actual (non-Immutable) array after executing:
state.get('results')
(e.g. in a selector defined via reselect)
Why?
From the fromJS docs:
Deeply converts plain JS objects and arrays to Immutable Maps and Lists.
That said, fromJS({ results: [] }) is equal to Map({ results: List([])})
as soon as you call state.set('results', []), you replace List([]) with plain array [].
This is a classical trap for new starters.
The way I see it is you have to choose to either always use List there, or plain array.
List way:
Initialize: const state = fromJS({results: []})
Reduce: return state.update("results", list => list.clear())
Array way #1:
Initialize: const state = fromJS({results: null})
INIT reduce (dispatched once, to init state) return state.set("results", [])
Reduce: return state.set("results", [1, 2, 3])
Array way #2:
Initialize: const state = Map({results: []})
Reduce: return state.set("results", [1, 2, 3])
I'd recommend to always use Array way #2. It adds more code, as you have to control List / Map for each state's field, but it guarantees you that you get exactly what you want.
In the end it comes something like this:
const initialState = Map({
results: [],
some_field_as_list: List([]),
some_field_as_map: Map([]),
... // etc
});
fromJS({ results: [] }) is equal to Map({ results: List([])}),
Using state.set('results', []) you replace List([]) with plain []
I found this helpful in this case:
Initialize: const state = fromJS({results: []})
Reduce: return state.set('results', List([1,2,3]));
It was better than using plain array because later you can use setIn function editing the same state variable:
return state.setIn(['results',state.get("results").size], nextElement);
or
return state.setIn(['results',-1], lastElement);

Sailsjs MVC map params from external API to multiple models

I need to create a database of shopify orders so I can run advanced queries and sales reports that you can't do in the shopify admin area. I'm building in Sails .12 and mysql. Shopify lets you register a webhook so that every time an order is placed, it creates a POST to the specified URL with the order data in the body as JSON. The products ordered are an array of JSON objects as one of the values in the POST:
{
"id": 123456,
"email": "jon#doe.ca",
"created_at": "2017-01-10T14:26:25-05:00",
...//many more entires
"line_items": [
{
"id": 24361829895,
"variant_id": 12345,
"title": "T-Shirt",
"quantity": 1,
"price": "140.00",
},
{
"id": 44361829895,
"variant_id": 42345,
"title": "Hat",
"quantity": 1,
"price": "40.00",
},
]
}
I need to save the order into an Orders table, and the products ordered into a line_items table that is a one to many relation; one order can have many line_items (products ordered). There are over 100 key-value pairs sent by the webhook, and I'm saving all of it. I've created my two models where I define the data type, so now i have very long Order.js and Line_item.js files, and I'm using the
line_items: {
collection: 'line_item',
via: 'order_id'
},
in my Order.js, and
order_id: {
model: 'order'
},
in my Line_item.js models to relate them. Is this the correct way to denfine my two tables? Also, where would I put the code that maps the JSON to the model parameters? If I put that code in the controllers, would I have to type another 100+ lines of code to map each json value to its correct parameter. The how would I save to the two different models/tables? Eg:
var newOrder = {};
newOrder.id =req.param('id');
newOrder.email = req.param('email');
newOrder.name = req.param('name');
...//over 100 lines more, then Order.create(newOrder, ...)
var newLine_items = req.params('line_items'); //an array
_.forEach(newLine_items, function(line_item){
var newLine_item = {};
newLine_item.id = line_item.id;
newLine_item.order_id = newOrder.id;
newLine_item.title = line_item.title;
//etc for over 20 more lines, then Line_item.create(newLine_item, ...)
});
I need to save the order into an Orders table, and the products ordered into a line_items table that is a one to many relation; one order can have many line_items (products ordered).
That sounds completely reasonable, well, besides the use of the Oxford comma :)
There are over 100 key-value pairs sent by the webhook
I'm not sure that I understand exactly what this is or what it is used for within this process.
That being said, it might help to have a single attribute in your model for this which has a JSON value, then retrieve and work with it as JSON instead of trying to manually account for each attribute if that is what you're doing over there?
It really depends on your use case and how you'll use the data though but I figure if the format changes you might have a problem, not so if it's just being stored and parsed as a JSON object?
Also, where would I put the code that maps the JSON to the model parameters
In v0.12.x take a look at Services.
In v1, Services will still work but moving this logic into Helpers might be a good option but then, it seems that a custom model method would be a better one.
Here is a shorter version of your code:
var newOrder = req.allParams();
newLine_items = {};
_.forEach(newOrder.line_items, function(line_item) {
newLine_items.push(line_item);
});
Here is what your logic might look like:
var newOrder = req.allParams();
// Store the order
Order
.create(newOrders)
.exec(function (err, result) {
if (err) // handle the error
var newLine_items = {};
_.forEach(newOrder.line_items, function(line_item) {
// Add the order id for association
line_item.order_id = result.id;
// Add the new line item with the orders id
newLine_items.push(line_item);
});
// Store the orders line items
LineItems
.create(newLine_items)
.exec(function (err, result) {
if (err) // handle the error
// Handle success
});
});
And the lifecycle callback in the Order model:
beforeCreate: function (values, cb) {
delete(values.line_items);
cb();
}
But you really should look into bluebird promises as the model methods in version one of sails have opt in support for them and it helps to negate the pyramid of doom that is starting in my example and is also something that you want to avoid :P