I have a form that I can create duplicate sections. Right now when the form is submitted the form spits out one giant object. I'm working on a filter function that can filter out the duplicate sections and organize the duplicates in array so it works with my API.
e.g.
// base object
{
question_10: ""
question_10a: ""
question_11_checkbox: false
question_11_checkbox_copy_0: false
question_11_checkbox_copy_1: true
question_11_text: "110 Monroe St"
question_11_text_copy_0: "186 Aspen Road"
question_12_checkbox: false
question_12_checkbox_copy_0: false
question_12_text: "New York"
question_12_text_copy_0: "South Orange"
...
}
// what I want is
{
question_10: ""
question_10a: ""
question_11_checkbox: false
question_11_checkbox_copies: [
{ question_11_checkbox_copy_0: false }
{ question_11_checkbox_copy_1: true }
]
question_11_text: "101 Monroe St"
question_11_text_copies: [
{ question_11_text_copy_0: "186 Aspen Road"}
]
question_12_checkbox: false
question_12_checkbox_copies: [
{ question_12_checkbox_copy_0: false}
]
question_12_text: "New York"
question_12_text_copies: [
{ question_12_text_copy_0: "South Orange"}
]
...
}
So far I've been able to filter out the copies from the original object and create the arrays for the copies
// filter out copy keys
const copiesKey = Object.keys(data).filter(key => key.includes('copy'));
const copy = {};
// create arrays for copies
copiesKey.map(copiedQuestion => {
if (!(`${copiedQuestion.slice(0, copiedQuestion.length - 7)}_copies` in copy)) {
copy[`${copiedQuestion.slice(0, copiedQuestion.length - 7)}_copies`] = [];
}
});
Where I'm stuck is it's not clear how to match the object to the appropriate array and push it.
e.g.
question_11_text_copies: [
{ question_11_text_copy_0: "186 Aspen Road" }
]
So far I've tried to slice the last three keys of the copy_0 object and match the key to the array name by using array.filter, but that didn't work as expected.
How can I match the 'copy_n' objects to the appropriate array and push those objects to the array?
This might be a bit old fashioned, but using a for...in loop in combination with regex pattern matching against the key might be the clearest solution.
const data = {
question_10: "",
question_10a: "",
question_11_checkbox: false,
question_11_checkbox_copy_0: false,
question_11_checkbox_copy_1: true,
question_11_text: "110 Monroe St",
question_11_text_copy_0: "186 Aspen Road",
question_12_checkbox: false,
question_12_checkbox_copy_0: false,
question_12_text: "New York",
question_12_text_copy_0: "South Orange",
};
const copy_n = /^(.*)_copy_(\d+)$/;
const result = {};
for (const key in data) {
const value = data[key];
const match = key.match(copy_n);
if (match) {
const copies_key = `${match[1]}_copies`;
const index = parseInt(match[2], 10);
result[copies_key] ||= [];
result[copies_key][index] = { [key]: value };
} else {
result[key] = value;
}
}
console.log(result);
The pattern /^(.*)_copy_(\d+)$/ matches anything that ends with _copy_ followed by 1 or more decimals. Everything before _copy_ is placed in capture group 1, the decimals behind _copy_ are placed in capture group 2.
If there is no match (else scenario) we simply assign the value to the same key.
If there is a match (if scenario) we first determine the collection key (copies_key) and the index to use. We then check if result[copies_key] is already set (we check if it's a truthy value to be precise). If it's not, we assign it to an empty array. After that we use the index to assign an object to the correct index in the array.
You can reduce to object's entries to a new object. For each key find if it has a base key (the one before "copy") using a RegExp with lookahead. If it doesn't has a base key, add it directly to the accumulator. If it does, add it to the appropriate "copies" array (initialize it if needed using Logical nullish assignment ??=) (TS playground).
const fn = obj => Object.entries(obj)
.reduce((acc, [k, v]) => {
const [baseKey] = k.match(/.*(?=_copy_)/) ?? [] // get the key from an item with copy
if(!baseKey) acc[k] = v // if no baseKey add the item to the accumulator
else {
const copiesKey = `${baseKey}_copies`
acc[copiesKey] ??= [] // if no copies key add a new one to the accumulator
acc[copiesKey].push({ [k]: v }) // create an object and push to the current copies key
}
return acc
}, {})
const obj = {"question_10":"","question_10a":"","question_11_checkbox":false,"question_11_checkbox_copy_0":false,"question_11_checkbox_copy_1":true,"question_11_text":"110 Monroe St","question_11_text_copy_0":"186 Aspen Road","question_12_checkbox":false,"question_12_checkbox_copy_0":false,"question_12_text":"New York","question_12_text_copy_0":"South Orange"}
const result = fn(obj)
console.log(result)
Related
combine each irritation of valueArr it's inner member to a matching key from keysArr,
Index will always match each other.
the result I'm looking for: the key for each object and the id is equal to "internalid"
But i will work it out if it's not clear
const valuesArr =
["10","9","Item","Bank","2","true","true","Result7","5675"],
["9","1","Expenses","OthExpense","4","true","true","9999","9999"],
["8","8","Expenses","OthAsset","6","false","false","6666","77777"],
["7","8","Expenses","AcctPay","4","true","true","666","7777"],
["6","123","123","123","123","123","123","123","123"],
["5","123","123","123","123","123","123","123","123"],
["4","Test1","Item","OthCurrAsset","2","Result5","Result6","ytrytrytryrty","32432"],
["2","Result1","Result2","Result3","Result4","Result5","Result6","Result7","Result8"],
["1","Test1","Test12","Test13","Test14","Test15","Test16","Test17","Test18"]]
const keysArr =
["internalid",
"custrecord_st_segment_transaction_type",
"custrecord_st_segment_sublist",
"custrecord_st_segment_account_type",
"custrecord_st_segment_subsidiary",
"custrecord_st_segment_department",
"custrecord_st_segment_class",
"custrecord_st_segment_location",
"custrecord_st_segment_custom_segment"]
result:
{"1":{"id":"1","values":{"internalid":"1","custrecord_st_segment_transaction_type":"Test1","custrecord_st_segment_sublist":"Test12","custrecord_st_segment_account_type":"Test13","custrecord_st_segment_subsidiary":"Test14","custrecord_st_segment_department":"Test15","custrecord_st_segment_class":"Test16","custrecord_st_segment_location":"Test17","custrecord_st_segment_custom_segment":"Test18"}},
"2":{"id":"2","values":{"internalid":"2","custrecord_st_segment_transaction_type":"Result1","custrecord_st_segment_sublist":"Result2","custrecord_st_segment_account_type":"Result3","custrecord_st_segment_subsidiary":"Result4","custrecord_st_segment_department":"Result5","custrecord_st_segment_class":"Result6","custrecord_st_segment_location":"Result7","custrecord_st_segment_custom_segment":"Result8"}},
"4":{"id":"4","values":{"internalid":"4","custrecord_st_segment_transaction_type":"Test1","custrecord_st_segment_sublist":"Item","custrecord_st_segment_account_type":"OthCurrAsset","custrecord_st_segment_subsidiary":"2","custrecord_st_segment_department":"Result5","custrecord_st_segment_class":"Result6","custrecord_st_segment_location":"ytrytrytryrty","custrecord_st_segment_custom_segment":"32432"}},
"5":{"id":"5","values":{"internalid":"5","custrecord_st_segment_transaction_type":"123","custrecord_st_segment_sublist":"123","custrecord_st_segment_account_type":"123","custrecord_st_segment_subsidiary":"123","custrecord_st_segment_department":"123","custrecord_st_segment_class":"123","custrecord_st_segment_location":"123","custrecord_st_segment_custom_segment":"123"}},
"6":{"id":"6","values":{"internalid":"6","custrecord_st_segment_transaction_type":"123","custrecord_st_segment_sublist":"123","custrecord_st_segment_account_type":"123","custrecord_st_segment_subsidiary":"123","custrecord_st_segment_department":"123","custrecord_st_segment_class":"123","custrecord_st_segment_location":"123","custrecord_st_segment_custom_segment":"123"}},
"7":{"id":"7","values":{"internalid":"7","custrecord_st_segment_transaction_type":"8","custrecord_st_segment_sublist":"Expenses","custrecord_st_segment_account_type":"AcctPay","custrecord_st_segment_subsidiary":"4","custrecord_st_segment_department":"true","custrecord_st_segment_class":"true","custrecord_st_segment_location":"666","custrecord_st_segment_custom_segment":"7777"}},
"8":{"id":"8","values":{"internalid":"8","custrecord_st_segment_transaction_type":"8","custrecord_st_segment_sublist":"Expenses","custrecord_st_segment_account_type":"OthAsset","custrecord_st_segment_subsidiary":"6","custrecord_st_segment_department":"false","custrecord_st_segment_class":"false","custrecord_st_segment_location":"6666","custrecord_st_segment_custom_segment":"77777"}},
"9":{"id":"9","values":{"internalid":"9","custrecord_st_segment_transaction_type":"1","custrecord_st_segment_sublist":"Expenses","custrecord_st_segment_account_type":"OthExpense","custrecord_st_segment_subsidiary":"4","custrecord_st_segment_department":"true","custrecord_st_segment_class":"true","custrecord_st_segment_location":"9999","custrecord_st_segment_custom_segment":"9999"}},
"10":{"id":"10","values":{"internalid":"10","custrecord_st_segment_transaction_type":"9","custrecord_st_segment_sublist":"Item","custrecord_st_segment_account_type":"Bank","custrecord_st_segment_subsidiary":"2","custrecord_st_segment_department":"true","custrecord_st_segment_class":"true","custrecord_st_segment_location":"Result7","custrecord_st_segment_custom_segment":"5675"}}]
I think the OP is asking how to "zip" two arrays, where one has keys and one has values. If so, Object.fromEntries() is very useful.
A simple zip, goes like this:
// a simple zip
function zip(keys, values) {
return Object.fromEntries(
keys.map((key, index) => [key, values[index]])
);
}
The OP appears to want the lead element in the values array to be a specially named field in the new object as well as the key of the resulting object. Here, applying the simple zip...
function zip(keys, values) {
return Object.fromEntries(
keys.map((key, index) => [key, values[index]])
);
}
// the value array contains an "id" element at the start
// produce an object that looks like { id: number, values: {...} }
function objectFromValues(keys, values) {
return { id: values[0], values: zip(keys, values.slice(1)) }
}
const valuesArr = [
[1, "A", "B", "C"],
[2, "D", "E", "F"]
];
const keys = ["keyA", "keyB", "keyC"];
const result = valuesArr.reduce((acc, values) => {
acc[values[0]] = objectFromValues(keys, values);
return acc;
}, {});
console.log(result)
I have an array:
const arr1 = [
"Strongly positive",
"Positive",
"Neutral",
"Negative",
"Strongly negative"
];
and responses object:
const responses = [
{ values: { QID16: 3 } },
{ values: { QID16: ["1", "2", "3"] } },
{ values: { QID16: 1 } }
];
The goal is to create a function that returns a map.
The map's keys are the elements of arr1 and the values are the count of those elements if their index + 1 appears in the responses object.
For example, responses[0].values.QID16 is 3 which is Neutral. (Neutral has index 2)
The problem is when the values in responses is an array like in responses[1]
I created the following function:
function getCounts(mainId, choices, responses) {
let choicesCounts = new Map();
choices.forEach((choice, i) => {
choicesCounts.set(choice, 0);
const id = i + 1;
responses.forEach((response) => {
if (response.values[mainId] && response.values[mainId] === id) {
choicesCounts.set(choice, choicesCounts.get(choice) + 1);
}
if (
response.values[mainId] &&
Array.isArray(response.values[mainId]) &&
response.values[mainId].includes(id.toString())
) {
// this is the part where I need help
response.values[mainId].forEach((n) => {
choicesCounts.set(
choices.at(parseInt(n, 10) - 1),
choicesCounts.get(choices.at(parseInt(n, 10) - 1)) + 1
);
});
}
});
});
return choicesCounts;
}
It would be called like this:
console.log(getCounts("QID16", arr1, responses));
Desired output:
// desired output is a map not an object
const desiredOutput = {
"Strongly positive": 2, // number 1 appears twice in responses
Positive: 1, // number 2 appers once in responses
Neutral: 2,
Negative: 0,
"Strongly negative": 0
};
It works in the case where the values are numbers but not when they're arrays.
What is wrong with this function? Any suggestions to make it simpler?
I think your problem lies in the way how you approached to solve this problem.
Currently you are iterating over your responses array once for each possible value from arr1.
When you encounter the array of values within a responses entry, you check whether it contains a value you are currently "looking for", and then increment ALL choices that were made. Since this will get looped over several times, you will also increment all of them several times and get the wrong counts.
Without giving you the exact code for changing this, just restructuring your function this way should already help coming to a cleaner solution:
Change the processing order. Instead of searching for individual values in your responses, try to do a "pre-processing" step where you create a map which represents the count of each unique key you encounter in the responses values.
The result will be almost what you were looking for, only the keys are not mapped to the values of arr1 yet. This has the advantage of only requiring a single iteration over your responses array, getting rid of the problem of counting some values multiple times.
In case you chose this approach because the responses values might contain values you are not interested in and you want to "skip" those, that behaviour can be replicated in the pre-processing phase by checking if the currently inspected value is contained in the arr1 array before writing it to the result map.
Does this cover the problem you were seeing, or was there an actual other error you encountered?
The OP should think about choosing an approach which breaks the entire task apart into smaller ones.
From the ratings-array (the one which literally names the ratings) create a rating-value (the ones that come with the response item's values) based map for looking up rating-keys (the rating-names which are the keys to the to be achieved rating-counts map).
const ratingValuesToKeys = new Map([
"Strongly positive",
"Positive",
"Neutral",
"Negative",
"Strongly negative",
].map((key, idx) => [idx + 1, key]));
console.log(Object.fromEntries([...ratingValuesToKeys]));
Also from the very same ratings-array create a rating-name based map for counting/summing up the occurrences of related rating-values.
const ratingCounts = new Map([
"Strongly positive",
"Positive",
"Neutral",
"Negative",
"Strongly negative",
].map(key => [key, 0]));
console.log(Object.fromEntries([...ratingCounts]));
Sanitize and collect all key specific rating-values for they are occurring as different types like number and/or string values as well as arrays.
const ratingValues = [
{ values: { QID16: 3 } },
{ values: { QID16: ["1", "2", "3"] } },
{ values: { QID16: 1 } },
]
.reduce((result, { values }) => {
if (values.hasOwnProperty('QID16')) {
const rating = values['QID16'];
if (Array.isArray(rating)) {
result
.push(
...rating
.map(value =>
parseInt(value, 10)
)
);
} else {
result
.push(parseInt(rating, 10));
}
}
return result;
}, []);
console.log({ ratingValues });
Based on all sanitized key specific rating-values do update each rating's occurrence by incrementing the related rating-key's count-value.
The final combined implementation and example code then might look similar to this ...
const ratings = [
"Strongly positive",
"Positive",
"Neutral",
"Negative",
"Strongly negative",
];
const responses = [
{ values: { QID16: 3 } },
{ values: { QID16: ["1", "2", "3"] } },
{ values: { QID16: 1 } },
];
function getRatingCounts(responseValueKey, ratings, responses) {
// create a rating-value based map for looking up rating-keys.
const ratingValuesToKeys = new Map(
ratings
.map((key, idx) => [idx + 1, key])
);
// create a rating-key based map for counting/summing up ratings.
const ratingCounts = new Map(
ratings
.map(key => [key, 0])
);
// sanitize and collect all key specific rating-values
// for they are occurring as different types ... like
// number and/or string values as well as arrays.
const ratingValues = responses
.reduce((result, { values }) => {
if (values.hasOwnProperty(responseValueKey)) {
const rating = values[responseValueKey];
if (Array.isArray(rating)) {
result
.push(
...rating
.map(value =>
parseInt(value, 10)
)
);
} else {
result
.push(parseInt(rating, 10));
}
}
return result;
}, []);
// based on all sanitized key specific rating-values
// do update each rating's occurrence by incrementing
// the related rating-key's count-value.
ratingValues
.forEach(value => {
const ratingKey = ratingValuesToKeys.get(value);
const ratingCount = ratingCounts.get(ratingKey);
ratingCounts.set(ratingKey, ratingCount + 1);
});
return ratingCounts;
}
const ratingCounts = getRatingCounts('QID16', ratings, responses);
console.log({
ratingCounts,
'counts as entry list': [...ratingCounts],
'counts as object': Object.fromEntries([...ratingCounts]),
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
I am new to JSON and want to re-structure my JSON response,
JSON-structure now-
{
"map": {
"Cityname": "[Jammu, Srinagar]",
"Pincode": "[180001, 190001]"
}
}
How I need it to be-
[
{ Cityname: "Jammu", Pincode: 180001},
{ Cityname: "Srinagar", Pincode: 190001}
]
Is there a way to do so, I searched for some possible solutions but was unable to do so.
Here is a Dynamic way of doing so, should work with any json string with the same layout. I fixed some of your json string.
// first we use JSON.parse() to turn a json string into a JS Object.
const data = JSON.parse(`{ "map": { "cityName": ["Jammu", "Srinagar"], "pinCode": [180001, 190001]}}`);
// cities will hold our city objects
const cities = [];
// Object.entries(theObject) returns theObjects keys and values as an array
// EG:
// [[key, value], [key, value]]
// forEach() loops through each key, value pair.
// Because we know that we are going to get a key value pair,
// I destructured the array into it's key and values using:
// [key, values] = [properties];
Object.entries(data.map).forEach((prop) => {
const [key, values] = prop;
values.forEach((value, index) => {
// now we check that the current index is an object.
// we do this because we can't add a property and value otherwise.
if(typeof cities[index] != "object"){
cities[index] = {};
}
// now we set the current value
cities[index][key] = value;
})
})
console.log(cities);
Your JSON response, its not quite logical because there is not mapping between city and pincode. I assumed that cityname and pincode are in the same order in the arrays. I used exact json structure you provided in the question.
You can skip additional steps substring and parse if your json data have correct data types (Cityname string array / Pincode int array).
const json = {
"map": {
"Cityname": "[Jammu, Srinagar]",
"Pincode": "[180001, 190001]"
}
}
const someFunc = () => {
let output = [];
const {Cityname, Pincode} = json.map;
const citynameArray = (Cityname.substring(1, Cityname.length-1)).split(",");
const pincodeArray = JSON.parse(Pincode);
citynameArray.map((v,i) => {
output.push({Cityname: v, Pincode: pincodeArray[i]})
});
return output;
}
console.log(someFunc());
I have a function that is using eval to convert a string with an expression to an object based on the parameter.
let indexType = ["Mac", "User", "Line", "Mask", "Ip", "Location"]
const filterIndex = (item) => {
filteredIndexSearch = []
eval(`search${item}`).forEach((e) => filteredIndexSearch.push(searchData[e.key]))
}
filterIndex(indexType[searchTotal.indexOf(Math.max(...searchTotal))])
searchData is an array that returns values based on the user input.
searchTotal is an array with the length of each search{item} array.
The filterIndex function takes the highest value from the searchData array and corresponds it to the indexType array, then use eval to convert the string to an object to pass the value to the filteredIndexSearch array.
What would be a better alternative to eval?
EDIT
To add more information on what this does:
searchData = [
[
{
key: 1,
data: "0123456789101"
},
{
key: 1,
data: "John Smith"
}
],
[
{
key: 2,
data: "0123456789102"
},
{
key: 2,
data: "Jane Smith"
},
]
]
const search = (data, key, container) => {
if (!data) data = "";
if (data.toLowerCase().includes(string)) {
container = container[container.length] = {
key: key,
data: data
}
}
}
const returnSearch = () => {
for (let i = 0; i < searchData.length; i++) {
search(searchData[i][0].data, searchData[i][0].key, searchMac)
search(searchData[i][1].data, searchData[i][1].key, searchUser)
}
}
returnSearch()
The data is incomplete, but hopefully conveys what I'm trying to do.
search will take the user input, and store the information in the corresponding array. If I input "Jo", it will return the searchUser array with only the "John Smith" value and all the other values with the same key. Inputting "102" returns the searchMac with the "0123456789102" value and all other values with the same key.
At the end of the day. I just want to convert search${parameter} to an object without using eval.
Move your global arrays into an object.
Somewhere it appears that you're defining the arrays, something like:
searchMac = [...];
searchUser = [...];
...
Instead of defining them as individual arrays, I'd define them as properties in an object:
searchIndices.Mac = [...];
searchIndices.User = [...];
...
Then, instead of using eval, your can replace your eval().forEach with searchIndices[item].forEach.
If the order of your search isn't important, your can instead loop through the keys of searchIndices:
Object.keys(searchIndices).forEach(item => {
searchIndices[item].forEach(...);
});
This ensures that if you ever add or drop an entry in searchIndices, you won't miss it or accidentally error out on an undefined search index.
Any time you have a situation with variables named x0, x1 etc, that should be a red flag to tell you you should be using an array instead. Variable names should never be semantically meaningful - that is code should never rely on the name of a variable to determine how the code behaves. Convert search0 etc into an array of search terms. Then use:
const filterIndex = (item) => search[item].map(i => searchData[i.key]);
filteredIndexSearch = filterIndex(indexType[searchTotal.indexOf(Math.max(...searchTotal))]);
(simplifying your code). Note that in your code, filteredIndexSearch is modified inside the arrow function. Better to have it return the result as above.
There are just 2 objects in an array where I need your attention at. I'm trying to find a way to merge these two objects into a single object based on item.Symbol and then add the values of their corresponding item.TotalCost, item.Price, and item.Quantity to the new object.
I tried doing a Set function but they just merge into 1 object and the values aren't added. I'm receiving an object like this:
[{
CompanyName: "Microsoft Corp."
Date: 1606503905
Price: 215.23
Quantity: 50
Symbol: "MSFT"
TotalCost: 10761.5
},
{
CompanyName: "Microsoft Corp."
Date: 1606503913
Price: 215.23
Quantity: 25
Symbol: "MSFT"
TotalCost: 5380.75
}
]
Here is my code so far:
let set = new Set()
const newSet = Objects.filter(item => {
if (!set.has(item.Symbol)) {
set.add(item.Symbol)
return true;
}
return false},set
)
console.log(newArray)
Note that the Objects variable contains the array of objects shown in the first code block.The set function I wrote only merges them into a single object based on their Symbol but I don't know where to go from here. I'm praying to the Javascript Overlords and Es6 Sith Lords to heed my call. May the force be with you.
Reduce the array of objects to an object, using the Symbol property as the key. If the key doesn't exist on the object, assign a clone of the current item to the key. If it exists, add the current item's values to the existing object:
const arr = [{"CompanyName":"Microsoft Corp.","Date":1606503905,"Price":215.23,"Quantity":50,"Symbol":"MSFT","TotalCost":10761.5},{"CompanyName":"Microsoft Corp.","Date":1606503913,"Price":215.23,"Quantity":25,"Symbol":"MSFT","TotalCost":5380.75}]
const newArray = Object.values(
arr.reduce((acc, o) => {
if(!acc[o.Symbol]) acc[o.Symbol] = { ...o }
else {
acc[o.Symbol].Price += o.Price
acc[o.Symbol].Quantity += o.Quantity
acc[o.Symbol].TotalCost += o.TotalCost
}
return acc
}, {})
);
console.log(newArray)