I have an array from API call response that looks like this:
[
{
"IsDatalakeEnabled": true,
"RecoveryHr": {
"IsRecoveryHREnabled": false,
"RecoveryHeartRateInterval": 120,
"RecoveryHeartRateSessionTime": 300
}
}
]
I need to get each key:value pair and make assertion that each one exists in other API call response body that looks like this:
...
"StudioAddress": null,
"StudioProfileLanguage": {
"LanguageName": "English",
"LanguageCode": "en"
},
"IsDiversityChannel": true,
"TotalDiversityRadios": 2,
"IsDatalakeEnabled": false,
"IsNetpulse": false,
"RecoveryHeartRateInterval": 120,
"RecoveryHeartRateSessionTime": 300,
"IsRecoveryHREnabled": false,
"StudioPhysicalLocationId": null,
"StudioLocation": null,
"IsIntroCapacityEnabled": false,
"Status": null,
"IsWeb": false,
"OrangeBook": null,
"IsFeReservationEnabled": true,
"TimeZone": "America/New_York",
"IsModifyMaxHr": false,
"IsRunRowEnabled": false,
"WeightMeasure": "LB",
...
Tried cy.each(), but it treats it like one object:
{
"TotalDiversityRadios": "0",
"IsDatalakeEnabled": true
}
Any suggestions/hints would be greatly appreciated. thank you!
tried to convert response body object into array using _.castArray()
Also, tried Object.entries()
Update 01/14/2023
#adoff This is how my test looks like:
validateStudioSettings() {
cy.intercept('POST', '**/studio-settings').as('post')
cy.contains('APPLY').should('be.enabled').click()
cy.get('.dialog-content').should('be.visible').and('have.text', 'Are you sure you want to apply these settings?')
cy.contains('AGREE').click()
cy.get('.notification').should('be.visible').and('contain', 'Success').and('contain', 'Update studio settings successfully.')
cy.wait('#post').then(post => {
expect(post.response.statusCode).equal(200)
expect(post.response.statusMessage).equal('OK')
expect(post.response.body).equal('Update studio settings successfully.')
let studios = _.castArray(post.request.body.StudioIds)
let settings = _.castArray(post.request.body.Settings)//.map(([key, val]) => key + ': ' + val);
console.log('Studios: ', studios)
console.log('Settings: ', settings)
cy.wrap(studios).each((studioId, index) => {
console.log('StudioId: ', studioId, index)
let idToken = localStorage.getItem("idToken")
cy.request({
method: "GET",
url: `/` + studioId,
headers: {
'authorization-cognito': idToken
}
}).then(response => {
cy.wrap(settings).each((setting) => {
console.log('Setting: ', setting)
cy.wrap(response).its('body').then(body => {
let el = _.castArray(body)
console.log('Body: ', el)
expect(el).to.contain(setting)
})
})
})
})
})
}
With this code I'm getting error in Cypress runner:
Cypress Error
Console
This could be done by checking key/value using Object.entries(obj) to give an array of key/value which would be compared to the expected object's key/value.
But there is nesting in the response.
Perhaps you could flatten the response first?
Here's a sample function that would help
const object = {
IsDatalakeEnabled: true,
RecoveryHr: {
IsRecoveryHREnabled: false,
RecoveryHeartRateInterval: 120,
RecoveryHeartRateSessionTime: 300,
},
};
function keyValues(obj) {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (typeof value === "object") {
const kvs = keyValues(value);
acc = acc.concat(kvs);
} else {
acc.push([key, value]);
}
return acc;
}, []);
}
console.log(keyValues(object))
/*
(4) [Array(2), Array(2), Array(2), Array(2)]
0: (2) ['IsDatalakeEnabled', true]
1: (2) ['IsRecoveryHREnabled', false]
2: (2) ['RecoveryHeartRateInterval', 120]
3: (2) ['RecoveryHeartRateSessionTime', 300]
length: 4
*/
In the test:
const expected = {
...
"IsDatalakeEnabled": false,
"IsNetpulse": false,
"RecoveryHeartRateInterval": 120,
"RecoveryHeartRateSessionTime": 300,
...
}
function keyValues(obj) {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (typeof value === "object") {
const kvs = keyValues(value);
acc = acc.concat(kvs);
} else {
acc.push([key, value]);
}
return acc;
}, []);
}
cy.request(...)
.then(response => response.json())
.then(data => {
const obj = data[0]
const kvs = keyValues(obj)
kvs.forEach([key, value] => {
expect(expected(key)).to.eq(value)
})
})
Related
I want the difference in such a way that the I don't return the entire nested object if any of the values is different.
I have seen solutions online and they all return the entire nested objects and it doesn't work if only 1 key-value pair is changed. i don't want to show the difference as a complete nested object. it should be easier for any user to read.
for eg:
const A = {
position: 2,
attributes: [{
code: 123,
name: "xyz",
params: {
label: "hehe",
units: "currency"
}
}],
code: 1
}
const B = {
position: 3,
attributes: [{
code: 123,
name: "xyzr",
params: {
label: "heh",
units: "currency"
}
}],
code: 1
}
I want the output to be like this:
difference: {
position: {
current: 2,
previous: 3
},
attributes: {
current : [{ name: "xyz", params: { label: "hehe" } }],
previous: [{ name: "xyzr", params: {label: "heh"}}]
}
}
The code that I tried:
const compareEditedChanges = (A: any, B: any) => {
const allKeys = _.union(_.keys(A), _.keys(B));
try {
setDifference(
_.reduce(
allKeys,
(result: any, key) => {
if (!_.isEqual(A?.[key], B?.[key])) {
result[key] = {
current: A[key],
previous: B[key]
};
}
return result;
},
{}
)
);
} catch (err) {
console.error(err);
}
return difference;
};
After giving it a lot of thought to the code, I came with my own solution for a deeply nested objects comparison and listing out the differences in an object with keys as current and previous.
I didn't use any inbuilt libraries and wrote the code with simple for loop, recursion and map
const compareEditedChanges = (
previousState,
currentState
) => {
const result = [];
for (const key in currentState) {
// if value is string or number or boolean
if (
typeof currentState[key] === 'string' ||
typeof currentState[key] === 'number' ||
typeof currentState[key] === 'boolean'
) {
if (String(currentState[key]) !== String(previousState[key])) {
result.push({
[key]: {
current: currentState[key],
previous: previousState[key]
}
});
}
}
// if an array
if (
Array.isArray(currentState[key]) ||
Array.isArray(previousState[key])
) {
console.log(currentState[key])
if (currentState[key].length > 0 || previousState[key].length > 0) {
currentState[key].map((value, index) => {
// check for array of string or number or boolean
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean'
) {
if (
JSON.stringify(currentState[key]) !==
JSON.stringify(previousState[key])
) {
result.push({
[key]: {
current: currentState[key],
previous: previousState[key]
}
});
}
}
// check for array of objects
if (typeof value === 'object') {
const ans = compare(
value,
previousState[key][index]
);
result.push(ans);
}
});
}
}
}
return result;
};
You first need a object:
const [object, setObject] = useState({
number: 0,
text: "foo"
});
You need to check when the object changed with useEffect, but you also need to see the previos object, for that we will be using a helper function.
const prevObject = usePrevious(object);
const [result, setResult] = useState("");
useEffect(() => {
if (prevObject) {
if (object.number != prevObject.number) {
setResult("number changed");
}
if (object.text != prevObject.text) {
setResult("text changed");
}
}
}, [object]);
//Helper function to get previos
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Here is the Codesandbox
I've read many similar questions and have tried a bunch of code. Unfortunately, I'm not getting my code to run :-(
So, the situation is as follows: In a route of a node.js server, I have to respond with a filtered array of Objects. Unfortunately, whatever I do, I always get an empty array [] back. The filter is a bit tricky in my opinion, as it consists of a string comparison AND an async call to a library function. With the console output, I can clearly see that the correct element is found, but at the same time I see that I've already received the object...
Here is some code that exemplifies my challenge:
let testArray = [
{
id: 'stringId1',
data: {
someDoc: {
moreContent: 'Some random content',
type: 'noInterest'
}
}
},
{
id: 'stringId2',
data: {
someDoc: {
moreContent: 'Some random content',
type: 'ofInterest'
}
}
},
{
id: 'stringId3',
data: {
someDoc: {
moreContent: 'Some random content',
type: 'ofInterest'
}
}
}
]
// code from a library. Can't take an influence in it.
async function booleanWhenGood(id) {
if (id in some Object) {
return { myBoolean: true };
} else {
return { myBoolean: false };
}
}
// Should return only elements with type 'ofInterest' and that the function booleanWhenGood is true
router.get('/', function(res,req) {
tryOne(testArray).then(tryOneResult =>{
console.log('tryOneResult', tryOneResult);
});
tryTwo(testArray).then(tryTwoResult => {
console.log("tryTwoResult ", tryTwoResult);
});
result = [];
for (const [idx, item] of testArray.entries() ) {
console.log(idx);
if (item.data.someDoc.type === "ofInterest") {
smt.find(item.id).then(element => {
if(element.found) {
result.push(item.id);
console.log("ID is true: ", item.id);
}
});
}
if (idx === testArray.length-1) {
// Always returns []
console.log(result);
res.send(result);
}
}
})
// A helper function I wrote that I use in the things I've tried
async function myComputeBoolean(inputId, inputBoolean) {
let result = await booleanWhenGood(inputId)
if (result.myBoolean) {
console.log("ID is true: ", inputId);
}
return (result.myBoolean && inputBoolean);
}
// A few things I've tried so far:
async function tryOne(myArray) {
let myTmpArray = []
Promise.all(myArray.filter(item => {
console.log("item ", item.id);
myComputeBoolean(item.id, item.data.someDoc.type === "ofInterest")
.then(myBResult => {
console.log("boolean result", myBResult)
if (myBResult) {
tmpjsdlf.push(item.id);
return true;
}
})
})).then(returnOfPromise => {
// Always returns [];
console.log("returnOfPromise", myTmpArray);
});
// Always returns []
return(myTmpArray);
}
async function tryTwo(myArray) {
let myTmpArray = [];
myArray.forEach(item => {
console.log("item ", item.id);
myCompuBoolean(item.id, item.data.someDoc.type === "ofInterest")
.then(myBResult => {
console.log("boolean result", myBResult)
if (myBResult) {
myTmpArray.push(item.did);
}
})
});
Promise.all(myTmpArray).then(promiseResult => {
return myTmpArray;
});
}
Asynchronous programming is really tough for me in this situation... Can you help me get it running?
I didn't inspect your attempts that closely, but I believe you are experiencing some race conditions (you print return and print the array before the promises resolve).
However you can alwayd use a regular for loop to filter iterables. Like this:
let testArray = [
{
id: 'stringId1',
data: {
someDoc: {
moreContent: 'Some random content',
type: 'noInterest'
}
}
},
{
id: 'stringId2',
data: {
someDoc: {
moreContent: 'Some random content',
type: 'ofInterest'
}
}
},
{
id: 'stringId3',
data: {
someDoc: {
moreContent: 'Some random content',
type: 'ofInterest'
}
}
}
]
async function booleanWhenGood(id) {
if (id in { 'stringId1': 1, 'stringId2': 1 }) { // mock object
return { myBoolean: true };
} else {
return { myBoolean: false };
}
}
async function main() {
let filtered = []
for (item of testArray)
if ((await booleanWhenGood(item.id)).myBoolean && item.data.someDoc.type === 'ofInterest')
filtered.push(item)
console.log('filtered :>> ', filtered);
}
main()
For creating an action at hasura I'm using the following node.js code (still at an experimental stage) in glitch.com -
const execute = async (gql_query, variables) => {
const fetchResponse = await fetch(
"https://example.com/v1/graphql",
{
method: "POST",
body: JSON.stringify({
query: gql_query,
variables: variables
})
}
);
// console.log('DEBUG: ', fetchResponse);
const data = await fetchResponse.json();
console.log("DEBUG: ", data);
return data;
};
// paste the code from codegen here
const ACTION_INSERT_PAYSLIP_GET_DRIVER_PAYMENT_DATA = `
query getDriverPaymentData ($orders: [Int!]!) {
company_order (where: {company_order_id: {_in: $orders}}) {
company_order_details (distinct_on: stage_cost_driver_id) {
stage_cost_driver_id
company_user {
delivery_salary
}
}
}
}`
// Request Handler
app.post('/action_insert_payslip', async (req, res) => {
// get request input
const { order_list } = req.body.input
console.log('Input', order_list)
const orders = order_list.order_id
console.log('Item: ', orders)
const { data:driverPaymentData, errors:driverPaymentError} = await execute(ACTION_INSERT_PAYSLIP_GET_DRIVER_PAYMENT_DATA, orders)
console.log('Driver Payment Data: ', driverPaymentData)
// run some business logic
// success
return res.json({
// payslip_list: "<value>"
payslip_list: order_list
})
});
The query getDriverPaymentData produces an output like the following in hasura api explorer:
{
"data": {
"company_order": [
{
"company_order_details": [
{
"stage_cost_driver_id": 1,
"company_user": {
"delivery_salary": 20
}
},
{
"stage_cost_driver_id": 6,
"company_user": {
"delivery_salary": 10
}
}
]
},
{
"company_order_details": [
{
"stage_cost_driver_id": 6,
"company_user": {
"delivery_salary": 10
}
}
]
}
]
}
}
But in the log, I'm getting the following output:
Input { order_id: [ 247, 260, 253 ] }
Item: [ 247, 260, 253 ]
DEBUG: { errors:
[ { extensions: [Object],
message:
'parsing HashMap failed, expected Object, but encountered Array' } ] }
Driver Payment Data: undefined
It says that it expects object but encountered array. But from what I see, I'm already getting an object "data": {[....]} with array inside it from the output at hasura's API console.
What am I missing here? How can I get the data of stage_cost_driver_id and delivery_salary?
Shouldn't variables be an object?
body: JSON.stringify({
query: gql_query,
variables: {orders: variables}
})
My data looks like this:
{
'004': [
{
year_week: '2020-W1',
actual_bank_amount: '6500000',
ext_in_rental_income: '',
ext_in_tax_refund: '',
ext_in_dividends_income: ''
},
{
year_week: '2020-W2',
actual_bank_amount: '6500000',
ext_in_rental_income: '',
ext_in_tax_refund: '',
ext_in_dividends_income: ''
}
],
'007': [
{
year_week: '2020-W22',
actual_bank_amount: '65050000',
ext_in_rental_income: '30000',
ext_in_tax_refund: '',
ext_in_dividends_income: ''
}
]
},
I am trying to update say date for year_week '2020-W1' in '004'.
No problem with action and reducer but data is not updated in the list.
Below is my reducer:
case 'UPDATE':
state.planningData[action.payload.currentSite].map((item, index) => {
if (item.year_week === action.payload.data.year_week) {
return Object.assign({}, item, action.payload.data);
}
return item;
});
console.log(state)
return {
loading: true,
planningData: state.planningData,
error: ''
}
What I am doing wrong please. Btw when I do console log or run redux extension I see the updated state.
Below is my action creator:
export const update = (data) =>
(dispatch, getState) => {
console.log("Update action called" + JSON.stringify(data))
const currentSite = getState().sites.currentSite;
dispatch({
type: 'UPDATE',
payload: {
data: data,
currentSite: currentSite
}
});
};
btw I am calling it from a editable cell component on "enter" and blur event below is my code
const save = async e => {
try {
const values = await form.validateFields();
toggleEdit();
dispatch(update({ ...record, ...values }));
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};
This isn't pretty but it works. You had a bit of nested data in your state and it wasn't being updated properly.
case "UPDATE":
let updatedPlanningData = {};
for (let prop in state.planningData) {
if (prop === action.payload.currentSite) {
updatedPlanningData[action.payload.currentSite] = state.planningData[
action.payload.currentSite
].map((item, index) => {
if (item["year_week"] === action.payload.data.year_week) {
return Object.assign({}, item, action.payload.data);
}
return item;
});
} else {
updatedPlanningData.prop = state.planningData[prop];
}
}
return {
loading: true,
planningData: updatedPlanningData,
error: ""
};
Here is example code in codesandbox
Edit: more compact solution
let updatedPlanningData = {...state.planningData};
updatedPlanningData[action.payload.currentSite].map((item, index) => {
if (item["year_week"] === action.payload.data.year_week) {
return Object.assign(item, action.payload.data);
}
return item;
});
I want to filter on the property of children object and return parents with children that passes the filter.
I tried with combination of Array.filter, Array.some, and Object.values, but I can't think of a way to get the key back once Ive used Object.values
var data = {
parent1: {
child1: {
source: true
},
child2: {
source: true
}
},
parent2: {
child3: {
source: true
},
child4: {
source: false
}
},
parent3: {
child5: {
source: false
}
}
}
I want the outcome to be:
var afterFilter = {
parent1: {
child1: {
source: true
},
child2: {
source: true
}
},
parent2: {
child3: {
source: true
}
}
}
If you want a solution with a reuseable function, I suggest looking at this implementation.
const data = {parent1:{child1:{source:true},child2:{source:true}},parent2:{child3:{source:true},child4:{source:false}},parent3:{child5:{source:false}}}
function objectMapReduce (object, map, filter) {
// iterate key-value pairs of object
return Object.entries(object).reduce(
(accumulator, [key, value]) => {
// map each value in object
const result = map(value, key, object)
// filter each mapped value
return filter(result, key, object)
? Object.assign(accumulator, { [key]: result })
: accumulator
},
// initial value of accumulator
{}
)
}
const afterFilter = objectMapReduce(
data, // map-reduce each parent in data
parent => objectMapReduce(
parent, // map-reduce each child in parent
({ source}) => ({ source }), // copy each child
({ source }) => source // keep child if source is true
),
parent => Object.keys(parent).length > 0 // keep non-empty parent
)
console.log(afterFilter)
Instead of using Array methods, you can also try a simple for...of loop:
var data = {
parent1: {
child1: {
source: true
},
child2: {
source: true
}
},
parent2: {
child3: {
source: true
},
child4: {
source: false
}
},
parent3: {
child5: {
source: false
}
}
}
var afterFilter = {};
for (const [key, value] of Object.entries(data)){
for (const [k, v] of Object.entries(value)){
const { source } = v;
if (source !== true)
continue;
// If `afterFilter[key]` does not exist, init with {}
afterFilter[key] = afterFilter[key] || {};
afterFilter[key][k] = { source };
}
}
console.log(afterFilter)
Try this using Array.reduce and Object.entries , for each parent entry iterate through the children of the parent object filter it based on the source.
If the current child of the parent has the source as true then add it to the accumulator acc of the reduce else ignore it:
const data = {parent1:{child1:{source:true},child2:{source:true}},parent2:{child3:{source:true},child4:{source:false}},parent3:{child5:{source:false}}};
const res = Object.entries(data).reduce((acc, [key, value]) =>{
for(child in value){ //value is the child-object of the parent, iterating throgh all the key of the child and checking if the source is true for this key of the child
if(value[child].source){
acc[key] = {...acc[key], [child] : value[child]}; //using spread operator to preserve previous values
}
}
return acc;
}, {});
console.log(res);
If you find to find whose children is true and return that parent, maybe this is correct answer for you
const data = [
{ name: 'parent1', parent : { child: { source : true } } },
{ name: 'parent2', parent : { child: { source : true } } },
{ name: 'parent3', parent : { child: { source : false } } }
];
const newData = data.filter((e)=> e.parent.child.source === true);
console.log(newData);
This is my solution. Try this
var data = {
parent1: {
child1: {
source: true
},
child2: {
source: true
}
},
parent2: {
child3: {
source: true
},
child4: {
source: false
}
},
parent3: {
child5: {
source: false
}
}
}
var afterFilter = {}
for(var key in data){
for(var childkey in data[key]){
if(data[key][childkey].source){
if(afterFilter[key])
afterFilter[key][childkey] = data[key][childkey]
else
afterFilter[key] = {[childkey]: data[key][childkey]}
}
}
}
console.log(afterFilter);