I am making a server using nodejs & express in which user can request some data and server send response. But, the data is array and I want to send a json response to the user. So, I used forEach() method of array and use Object.assign(), so that I can get object. But the problem is I cannot use 'index' argument of forEach() method while 'value' argument is properly getting used inside the callback function. When I use only 'index' argument, then my code runs ok but I want to use both arguments at the same time.
route.get('/getGPX/:number', passport.authenticate('jwt', { session: false }), (req, res) => {
gpx.find({ username: req.user.username }, (err, result) => {
if (err) console.log(err);
if (result) {
if (result.length === 0) {
res.end();
console.log('ended')
}
else {
var jsonRes = {};
result.forEach((value, index) => {
I can use 'value' but not 'index' from the arguments
jsonRes = Object.assign({ index: value.data }, jsonRes);
})
res.json({data: jsonRes});
}
}
})
I even tried using global var, but even it's not working, how can I use index as well as value argument at the same time
What is the JSON structure that you want ?
if you want a json like :
{
0: "my first value",
1: "second"
}
You just miss [] around index, here you put 'index' as a key not the value of index. So you override the index key in each iteration.
Here is the code that use the value of index as a key in a json.
jsonRes = Object.assign({[index]: value.data}, jsonRes)
See here for a working example with more examples : https://repl.it/#Benoit_Vasseur/nodejs-playground
Object.assign mutates the left-most parameter. it does not produce a new object. Since you are passing a literal object every time the jsonRes is going to be the last result.
Put jsonRes in the left instead - Object.assign(jsonRes, {index: value.data})
You might want to use a simple reduce instead of forEach and Object.assign:
} else {
var jsonRes = result.reduce((r, v, i) => {r[i] = v.data; return r}, {});
res.json({data: jsonRes});
}
Related
I have an object which returns,
[
{
name:"getSpeed",
params:["distance","time"]
},
{
name:"getTime",
params:["speed","distance"]
},
...
]
This object is subject to change as it is gathered from an embedded device.
im trying to convert this into an object with callable functions i.e.
let myObj = {
getSpeed: function(distance, time){
/* do something (this is irrelevant) */
},
getTime: function(speed, distance){
/* do something (again not relevant) */
}
}
is there any way to map an array of strings to function parameters when mapping over an array?
According to your comments it appears you want to create functions from this definition which send commands with a function-call-like string which is evaled on the other side, and you don't actually care about the paramater names but rather about the correct number of parameters.
I would therefore recommend something like this:
const myObj = Object.fromEntries(data.map(({ name, params }) => [
name,
(...args) => {
if (args.length !== params.length) {
throw new TypeError(`${name} expected ${params.length} arguments, got ${args.length}`)
}
this.UART.write(`${name}(${args.map(arg => JSON.stringify(arg)).join(',')})`)
}
]))
This will work with all the datatypes that JSON supports, and will as a side effect also pass undefined as argument correctly.
Here is a runnable example with console.log instead of this.UART.write:
const data = [
{
name: "getSpeed",
params: ["distance", "time"]
},
{
name: "getTime",
params: ["speed", "distance"]
}
]
const myObj = Object.fromEntries(data.map(({ name, params }) => [
name,
(...args) => {
if (args.length !== params.length) {
throw new TypeError(`${name} expected ${params.length} arguments, got ${args.length}`)
}
console.log(`${name}(${args.map(arg => JSON.stringify(arg)).join(',')})`)
}
]))
myObj.getSpeed(123, 456) // prints `getSpeed(123,456)`
myObj.getTime(123, 456) // prints `getTime(123,456)`
myObj.getTime("hello", true) // prints `getTime("hello",true)`
myObj.getTime(1) // throws `getTime expected 2 arguments, got 1`
However, as you said yourself, the whole eval business is not ideal anyway. I would recommend - if possible - to reconsider the protocol to use something more secure and robust like gRPC or, one layer below, protocol buffers. Given that you are using JavaScript on both ends, JSON-RPC could also be a nice solution.
Via a microservice, I retrieve several packages of JSON data and spit them out onto a Vue.js-driven page. The data looks something like this:
{"data":{"getcompanies":
[
{"id":6,"name":"Arena","address":"12 Baker Street","zip":"15090"},
{"id":7,"name":"McMillan","address":null,"zip":"15090"},
{"id":8,"name":"Ball","address":"342 Farm Road","zip":"15090"}
]
}}
{"data":{"getusers":
[{"id":22,"name":"Fred","address":"Parmesean Street","zip":"15090"},
{"id":24,"name":"George","address":"Loopy Lane","zip":"15090"},
{"id":25,"name":"Lucy","address":"Farm Road","zip":"15090"}]}}
{"data":{"getdevices":
[{"id":2,"name":"device type 1"},
{"id":4,"name":"device type 2"},
{"id":5,"name":"device type 3"}]}}
...and I successfully grab them individually via code like this:
getCompanies() {
this.sendMicroServiceRequest({
method: 'GET',
url: `api/authenticated/function/getcompanies`
})
.then((response) => {
if(response.data) {
this.dataCompanies = response.data.getcompanies
} else {
console.error(response)
}
}).catch(console.error)
}
...with getUsers() and getDevices() looking respectively the same. getCompanies() returns:
[{"id":6,"name":"Arena","address":"12 Baker Street","zip":"15090"},
{"id":7,"name":"McMillan","address":null,"zip":"15090"},
{"id":8,"name":"Ball","address":"342 Farm Road","zip":"15090"}]
...which I relay to the Vue template in a table, and this works just fine and dandy.
But this is obviously going to get unwieldy if I need to add more microservice calls down the road.
What I'm looking for is an elegant way to jump past the response.data.*whatever* and get to those id-records with a re-useable call, but I'm having trouble getting there. response.data[0] doesn't work, and mapping down to the stuff I need either comes back undefined, or in bits of array. And filtering for response.data[0].id to return just the rows with ids keeps coming back undefined.
My last attempt (see below) to access the data does work, but looks like it comes back as individual array elements. I'd rather not - if possible - rebuild an array into a JSON structure. I keep thinking I should be able to just step past the next level regardless of what it's called, and grab whatever is there in one chunk, as if I read response.data.getcompanies directly, but not caring what 'getcompanies' is, or needing to reference it by name:
// the call
this.dataCompanies = this.getFullData('companies')
getFullData(who) {
this.sendMicroServiceRequest({
method: 'GET',
url: 'api/authenticated/function/get' + who,
})
.then((response) => {
if(response) {
// attempt 1 to get chunk below 'getcompanies'
Object.keys(response.data).forEach(function(prop) {
console.log(response.data[prop])
})
// attempt 2
// for (const prop in response.data) {
// console.log(response.data[prop])
// }
let output = response.data[prop] // erroneously thinking this is in one object
return output
} else {
console.error(response)
}
}).catch(console.error)
}
...outputs:
(63) [{…}, {…}, {…}] <-- *there are 63 of these records, I'm just showing the first few...*
0: {"id":6,"name":"Arena","address":"12 Baker Street","zip":"15090"}
1: {"id":7,"name":"McMillan","address":null,"zip":"15090"},
2: {"id":8,"name":"Ball","address":"342 Farm Road","zip":"15090"}...
Oh, and the return above comes back 'undefined' for some reason that eludes me at 3AM. >.<
It's one of those things where I think I am close, but not quite. Any tips, hints, or pokes in the right direction are greatly appreciated.
I feel it's better to be explicit about accessing the object. Seems like the object key is consistent with the name of the microservice function? If so:
getData(functionName) {
return this.sendMicroServiceRequest({
method: 'GET',
url: "api/authenticated/function/" + functionName
})
.then( response => response.data[functionName] )
}
getCompanies(){
this.getData("getcompanies").then(companies => {
this.dataCompanies = companies
})
}
let arrResponse = {data: ['x']};
let objResponse = {data: {getcompanies: 'x'}};
console.log(arrResponse.data[0]);
console.log(Object.values(objResponse.data)[0]);
response.data[0] would work if data was an array. To get the first-and-only element of an object, use Object.values(response.data)[0] instead. Object.values converts an object to an array of its values.
Its counterparts Object.keys and Object.entries likewise return arrays of keys and key-value tuples respectively.
Note, order isn't guaranteed in objects, so this is only predictable in your situation because data has exactly a single key & value. Otherwise, you'd have to iterate the entry tuples and search for the desired entry.
firstValue
Let's begin with a generic function, firstValue. It will get the first value of an object, if present, otherwise it will throw an error -
const x = { something: "foo" }
const y = {}
const firstValue = t =>
{ const v = Object.values(t)
if (v.length)
return v[0]
else
throw Error("empty data")
}
console.log(firstValue(x)) // "foo"
console.log(firstValue(y)) // Error: empty data
getData
Now write a generic getData. We chain our firstValue function on the end, and be careful not to add a console.log or .catch here; that is a choice for the caller to decide -
getData(url) {
return this
.sendMicroServiceRequest({ method: "GET", url })
.then(response => {
if (response.data)
return response.data
else
return Promise.reject(response)
})
.then(firstValue)
}
Now we write getCompanies, getUsers, etc -
getCompanies() {
return getData("api/authenticated/function/getcompanies")
}
getUsers() {
return getData("api/authenticated/function/getusers")
}
//...
async and await
Maybe you could spruce up getData with async and await -
async getData(url) {
const response =
await this.sendMicroServiceRequest({ method: "GET", url })
return response.data
? firstValue(response.data)
: Promise.reject(response)
}
power of generics demonstrated
We might even suggest that these get* functions are no longer needed -
async getAll() {
return {
companies:
await getData("api/authenticated/function/getcompanies"),
users:
await getData("api/authenticated/function/getusers"),
devices:
await getData("api/authenticated/function/getdevices"),
// ...
}
}
Above we used three await getData(...) requests which happen in serial order. Perhaps you want all of these requests to run in parallel. Below we will show how to do that -
async getAll() {
const requests = [
getData("api/authenticated/function/getcompanies"),
getData("api/authenticated/function/getusers"),
getData("api/authenticated/function/getdevices")
]
const [companies, users, devices] = Promise.all(requests)
return { companies, users, devices }
}
error handling
Finally, error handling is reserved for the caller and should not be attempted within our generic functions -
this.getAll()
.then(data => this.render(data)) // some Vue template
.catch(console.error)
I'm trying to systematically replace the key value in a JSON object with a new value that I calculate:
newReport = filteredReport
sumFunc = (items, prop) => {
return items.reduce( function(a, b){
return a + b[prop];
}, 0);
}
complete = newReport.map((v,i) => {
let newValue = sumFunc(newReport[i].like, 'value')
//return { ...v, like: newValue }
return Object.assign({}, v, {like: newValue})
})
filteredReport, i.e., the original value, is a result of a query from MongoDB.
When I run this function, I get a response that's 340,000 lines long. The correct result is in place in the last line, but I get a lot of other undesired information in there that is unexpected.
Why do I receive this information?
This is a sample of the response: https://codepen.io/schoenbl/pen/pXVRBq?editors=0010#0
EDIT:
When I use spread and Object.assign, I receive the same result.
Another Edit:
When I preview it outside, it works correctly:
https://playcode.io/360520?tabs=script.js,preview,console
Last Edit:
This is my mongo query:
const report = await Report.find({createdAt: { $gt: startDate, $lt: Date() }})
.populate({path: 'like'})
.populate({
path: 'player',
populate: [{ path: 'team' },
{
path: 'team',
populate: {
path: 'league'
}
}
]
})
When you query it returns an array Mongoose document instance. But when you console log it shows you the toString representation of it. But actually, it contains all those functions properties of the Document class instance to do further operations. To return only data use .lean() when querying.
Report.find().lean().exec(function (err, report) {
....
}
This will return only the plain js object of the data that you want. Not all the class properties and methods.
Tip: To know what a variable really contains, use console.dir instead of console.log
I am mapping a subset of user data to an object of a refined data set. Inside the map i want to check if a variable is null or undefined, and if yes, then to set this variable to a placeholder value.
The issue I am facing is that declaring an if statement inside the map is causing an error, but even though a map can have an index as a parameter, how can we use it functionally with a conditional Statement? Any insight most appreciated.
return this.UserService.search(url)
.map((data) => {
console.log(data);
data.result = <any> data.result.map((user, index) => ({
// if statement causing error here
if(user.name === null || user.name === undefined){
// error with this if condition
},
id: user.id,
name: user.name,
type: user.type,
password: user.password,
}));
return data;
}).map(data => ({
meta: { totalItems: data.size },
data: data.result,
}));
You're attempting to use an object literal as the return type, but naturally, an if statement (or any statement) can't be inside object literal syntax.
So instead, define a function body, which also uses curly braces, and put your code inside with an explicit return statement.
// Starts function body instead of single expression-v
data.result = <any> data.result.map((user, index) => {
if (some_condition) {
return "some value"; // The object?
} else {
return "other value"; // A different object?
}
/*
// I assume these are to be used in the object you return
id: user.id,
name: user.name,
type: user.type,
password: user.password,
*/
});
You can express conditions in literal maps, but it is somewhat ugly.
return {
a: 1,
...(some_condition && {
b: 1,
})
};
As far as i know you can't do that with JUST a map.
however you could follow it up with a filter() function:
const newArray = oldArray.map((value, index) => condition ? value : null).filter(v => v);
you basicaly iterate over each item and then return the value or null depending on your condition.
Now once you have the map you just filter it by removing the null values from the array.
Notice that the original array is not altered and a new one is returned.
thanks for the idea #user8897421 for the idea. i just wanted to turn it into a one liner.
I want to use the zunionstore command on sets which i define at runtime, they are fetched dynamically so i never know what the sets are that i have to pass to the function.
syntax of zunionstore:
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
the parsed array contains the names of the sets.
client.zunionstore
(
'out',
parsed.length,
parsed,
function (err, res)
{
console.log(err);
if(!err)
{
client.zrevrange('out', 0, -1, 'withscores', function (err, res)
{
console.log(res);
if(!err)
{
//do stuff
}
});
}
}
);
as you can see i tried to pass the array containing the names but this doesn't work..
the error i get:
[Error: ERR syntax error]
Any ideas on how to solve this?
do you mean that you are having problems passing an array to a function? put all arguments into an array and call apply on the function: Passing an array as a function parameter in JavaScript
so, you have your parsed array, just add to it the other things like your 'out', parsed.length, etc, and call client.zunionstore.apply(this, array).