I have a JS Object that may look like one of the following:
// Example #1:
var data = {
product: {
value1: 'v1',
value2: 2
}
}
// Example #2:
var data = {
order: {
value1: 1
}
}
// Example #3:
var data = {
value1: 1
}
What I'd like to achieve:
var inputName = 'product[value1]';
var data = {
product: {
value1: 'v1',
value2: 2
}
}
var value = something(data, inputName); // should return v1
inputName and data can change, I can have any of the above data Objects with an inputName like 'product[value2]', 'order[value1]', 'value1'.
My guess is to use regex and get both attribute names. Is the a better way?
you can use underscore js _.each to iterate over the object like
_.each(data ,function(product){
console.log(product.value);
});
see the link: http://underscorejs.org/#each
you can also use for each loop.
Also you can perform filter like below:
_.filter(data, function(product){
return product.value;
});
One other way is to create a dictionary that directly caters to your search.
You can flatten your multi-level key value pair to create a dictionary that you can use readily. The below flatten function (taken from here) creates a dictionary like:
{
"product.value1": "v1",
"product.value2": 2
}
You can then just query it using dictionary["product.value1"]
This flatten function can be altered to format the keys like product[value1] if you wish so.
var data = {
product: {
value1: 'v1',
value2: 2
}
}
var myDictionary = flatten(data);
console.log(myDictionary);
console.log(myDictionary["product.value1"]);
console.log(myDictionary["product.something else"]);
function flatten(obj, opt_out, opt_paths) {
var out = opt_out || {};
var paths = opt_paths || [];
return Object.getOwnPropertyNames(obj).reduce(function(out, key) {
paths.push(key);
if (typeof obj[key] === 'object') {
flatten(obj[key], out, paths);
} else {
out[paths.join('.')] = obj[key];
}
paths.pop();
return out;
}, out)
}
If you know possible attribute names, then I would define an array with possible attribute names, then iterate over them checking if there is a field with this name
const names = [
'product',
'order'
];
function findValue(data){
if(data.value1) return data.value1;
for(let name in names){
if(data[name].value1) return data[name].value1;
}
}
Explanation
If you want to give your function a string like 'product[value1]' as argument you need to get all attribute values that you need to query for your result value. I did it with query.replace(/(\[)|(\])/g, ' ')split(' '). The returning array you need to check for empty strings and remove them. I did it with filter.
After that you can simply use reduce on the returned array to get on each iteration the new value. In the last iteration you have your result.
Code
function getDataValue(obj, query) {
var attributes = getAttributeNames(query)
return attributes.reduce(function(value, current) {
return value[current]
}, obj)
}
function getAttributeNames(query) {
return query.replace(/(\[)|(\])/g, ' ')
.split(' ')
.filter(function(string) {
return string.length > 0
})
}
Example
var dataOne = {
product: {
value1: 'v1',
value2: 2
}
}
var dataTwo = {
product: {
subProduct: {
value1: 'v2'
}
}
}
console.log(getDataValue(dataOne, 'product[value1]'))
console.log(getDataValue(dataTwo, 'product[subProduct][value1]'))
function getDataValue(obj, query) {
var attributes = getAttributeNames(query)
return attributes.reduce(function(value, current) {
return value[current]
}, obj)
}
function getAttributeNames(query) {
return query.replace(/(\[)|(\])/g, ' ')
.split(' ')
.filter(function(string) {
return string.length > 0
})
}
Related
I have 2 arrays. one is costHeadArray which is as
[
{
"label":"Flight",
"value":"179daf5f-2f89-1d12-40a2-e91a6a732774"
}
]
I am iterating response. My object value is as
{
taxInvoiceId: "b1b4d9ec-f20c-4a33-94c1-af7589e7b15a",
enquiryId: "3db0b4a8-5ab1-4c31-861a-73df36205716",
costHeadId: "179daf5f-2f89-1d12-40a2-e91a6a732774",
costSheetId: "01edd4a1-f1ad-a836-3a19-3a28271f1b57",
...
}
Now in same iteration, I need to check if costHeadArray value matches value in object. costHeadArray can have multiple json objects. only if value in costHeadArray matches with object then i need to enable flag. I tried using includes but it dont works . might be I am doing something wrong
My code
this.airInvoiceService.getTaxInvoiceLog(this.enquiryId).subscribe(
response => {
this.taxInvoiceLogs.unshift(...response);
for(const a of response) {
// {taxInvoiceId: "b1b4d9ec-f20c-4a33-94c1-af7589e7b15a", enquiryId: "3db0b4a8-5ab1-4c31-861a-73df36205716", enquiryRefId: "MTPL-2021-000074", costHeadId: "179daf5f-2f89-1d12-40a2-e91a6a732774", costSheetId: "01edd4a1-f1ad-a836-3a19-3a28271f1b57", …}
console.log(a);
//[{"label":"Flight","value":"e9bf7aa2-c730-8175-1cec-afcd239b0dff"}]
console.log(JSON.stringify(this.costHeadArray));
console.log(a.costHeadId);
if (Object.values(a).includes(this.costHeadArray)){
console.log('Gokul');
}
if (this.costHeadArray.includes(a.costHeadId)) {
console.log('Sam');
this.hideForm = true;
}
}
I have write simple script for you, please have a look, I hope it will help you
let costHeadArray = [
{
"label":"Flight",
"value":"179daf5f-2f89-1d12-40a2-e91a6a732774"
}
];
let myObject = {
taxInvoiceId: "b1b4d9ec-f20c-4a33-94c1-af7589e7b15a",
enquiryId: "3db0b4a8-5ab1-4c31-861a-73df36205716",
costHeadId: "179daf5f-2f89-1d12-40a2-e91a6a732774",
costSheetId: "01edd4a1-f1ad-a836-3a19-3a28271f1b57",
}
let hasCostHeadValue = costHeadArray.find(o => o.value == myObject.costHeadId) != null;
console.log(hasCostHeadValue);
///////////////////////////////////////////////////////////////////////////////////
Update your method like this.
this.airInvoiceService.getTaxInvoiceLog(this.enquiryId).subscribe(
response => {
this.taxInvoiceLogs.unshift(...response);
for(const a of response) {
// {taxInvoiceId: "b1b4d9ec-f20c-4a33-94c1-af7589e7b15a", enquiryId: "3db0b4a8-5ab1-4c31-861a-73df36205716", enquiryRefId: "MTPL-2021-000074", costHeadId: "179daf5f-2f89-1d12-40a2-e91a6a732774", costSheetId: "01edd4a1-f1ad-a836-3a19-3a28271f1b57", …}
console.log(a);
//[{"label":"Flight","value":"e9bf7aa2-c730-8175-1cec-afcd239b0dff"}]
console.log(JSON.stringify(this.costHeadArray));
console.log(a.costHeadId);
let hasCostHeadValue = this.costHeadArray.find(o => o.value == a.costHeadId) != null;
if(hasConstHeadValue){
} else {
}
// if (Object.values(a).includes(this.costHeadArray)){
// console.log('Gokul');
// }
// if (this.costHeadArray.includes(a.costHeadId)) {
// console.log('Sam');
// this.hideForm = true;
//}
}
Array.includes is comparing the array items by reference, not by value, thus:
For the array const values = [ {value: 1}, { value: 2} ]
values.includes(values[0]) will return true, because it re-uses the same {value:1}-object as the array and both objects have same reference ID.
values.includes({value: 1}) will return false, because the objects have the same content but different references.
You can use Array.findIndex instead:
const search = {value: 1};
const includes = values.findIndex(item => item.value === search.value) >= 0;
I have currently an object which contains a array on which I can iterate.
====== First Step =====
Iterate over an object's attribute instead of the object itself
I am wondering if there's a way to use :
const obj = new YMap();
obj.forEach(...);
instead of
const obj = new YMap();
obj.map.forEach();
EDIT : From VLAZ Comment, I can add this :
this.forEach = function(f) { this.maps.forEach(f);}
In my constructor so my forEach iterate over the maps attribute of my
object and not over my object itself.
===== Second Step =====
Avoid forEach imbrication to parse an object attribute of type Array of Map/List/Array
By extends, if I add a little more complexity on my object so it doesn't have an array but an array of list, is there a way to do :
const obj = new YMap();
obj.forEach(...);
instead of
const obj = new YMap();
obj.maps.foreach( function(map) {
map.forEach( function(item) {
...
}
});
To explain it differently, I wonder if there's a way that exist that allow me to have this data structure :
{
maps : [
[ 'A', 'B' ],
[ 'C', 'D' ],
[ 'E', 'F' ]
]
}
Where I could use the code :
obj.forEach(function(item) { console.log(item) });
that would output :
A B C D E F
without having to iterate over the array of array and then iterate on the items of each array using an imbricated forEach
My object looks like the following :
function YMap() {
this.maps = [new HashMap()];
Object.defineProperty(YMap.prototype, 'length', {get: function() {
...
}});
}
YMap.prototype.add = function() {...}
YMap.prototype.get = function() {...}
[...]
module.exports = YMap;
you can create custom iterable to loop onto object. I have created one using class based approach with help of Symbol.iterator property.
class YMap {
constructor() {
this.maps = [];
}
[Symbol.iterator]() {
let index = 0;
const that = this;
return {
next() {
let value = undefined;
let done = true;
if (index < that.maps.length) {
value = [...that.maps[index++]].join(" ")
done = false;
}
return {
value,
done
};
}
};
}
add(item) {
this.maps.push(item);
}
}
const obj = new YMap();
obj.add([23, 21]);
obj.add([25, 29]);
for (let c of obj) {
console.log(c, );
}
I have an object:
myObj = {
attendent-0-id:"123",
attendent-0-name:"Bob Smith",
attendent-1-id:"1234",
attendent-1-name:"Alex Smith",
attendent-2-id:"123",
attendent-2-name:"Charlie Smith",
attendent-maxGuest:1,
attendent-party-name:"",
}
I need to create a loop that go through myObj and find all the id's and then compares them for duplicates. So in this case it would log an error because attendent-0-id is equal to attendent-2-id.
If I do find duplicates I need to set a flag to true;
I have tried a bunch of things and am just stuck at this point. Thanks for any help.
In your case you can go through myObj using Object.keys() via:
for (const key of Object.keys(obj))
use a plain object as a map to store the previous values of the ids:
const map = {};
use a regex pattern to make sure only the specific ids are evaluated:
const pattern = /^attendent-\d+-id$/;
and then with the help of the map, log the error on duplicate ids:
if (value in map) {
console.error(`${map[value]} is equal to ${key}, which is ${value}`);
}
Example:
const myObj = {
'attendent-0-id': "123",
'attendent-0-name': "Bob Smith",
'attendent-1-id': "1234",
'attendent-1-name': "Alex Smith",
'attendent-2-id': "123",
'attendent-2-name': "Charlie Smith",
'attendent-maxGuest': 1,
'attendent-party-name': "",
};
function errorOnDuplicateIds(obj) {
const map = {};
const pattern = /^attendent-\d+-id$/;
for (const key of Object.keys(obj)) {
if (pattern.test(key)) {
const value = obj[key]
if (value in map) {
console.error(`${map[value]} is equal to ${key}, which is ${value}`);
} else {
map[value] = key
}
}
}
}
errorOnDuplicateIds(myObj);
const ids = []; // keep track of found ids
Object.keys(myObj).forEach(key => { // iterate over all properties of myObj
// check if property name is in format "attendent-" *some number* "-id"
if (/^attendent-\d+-id$/.test(key)) {
// check if the id has already been found
if (ids.findIndex(id => id === myObj[key]) !== -1) {
console.log('error');
} else {
ids.push(myObj[key]);
}
}
});
You can use Object.entries and a Map (keyed by value) for this:
var myObj = {"attendent-0-id":"123","attendent-0-name":"Bob Smith","attendent-1-id":"1234","attendent-1-name":"Alex Smith","attendent-2-id":"123","attendent-2-name":"Charlie Smith","attendent-maxGuest":1, "attendent-party-name":""};
var dupes = [...Object.entries(myObj).reduce(
(map, [key,val]) => map.set(val, (map.get(val) || []).concat(key)),
new Map
).values()].filter(keys => keys.length > 1);
console.log(dupes);
This solution does not give any particular meaning to the format of the keys.
Having said that, your object structure looks suspicious of bad design: you should not have enumerations in your object keys. For that you should use arrays.
Object.values(myObj) will create an array of all values and then you can use any way to find duplicate elements in that array.
var myValues = Object.values(myObj); //This will create an array of all values
var uniq = myValues.map((val) => {
return {count: 1, val: val}
}).reduce((a, b) => {
a[b.val] = (a[b.val] || 0) + b.count
return a
}, {});
var duplicates = Object.keys(uniq).filter((a) => uniq[a] > 1)
if (duplicates.length) {
return true;
} else {
return false;
}
My first advice would be to redefine your object to something more flexible.
let myObject = {
attendants : [
{
id: "123",
name: "Bob Smith"
},
{
id: "456",
name: "Alex Smith"
},
{
id: "768",
name: "Charlie Smith"
},
],
maxGuest: 1,
partyName: ""
};
This will allow you to iterate the attendants.
for (var attendant in myObject.attendants){
doSomething(attendant.id, attendant.name);
}
You can also sort the attendant:
// Sort by id
myObject.attendants.sort(function(left, right){
return left.value - right.value;
});
// Sort by name
myObject.attendants.sort(function (left, right){
var leftName = left.name.toLowerCase();
var rightName = right.name.toLowerCase();
if (leftName < rightName) return -1;
if (leftName > rightName) return 1;
return 0;
});
Now, lets assume you don't have a choice. Then it gets complicated.
You need to create (or modify an existent) a sort algorithm so it can use keys that are generated as:
myObject[`attendent-${index}-id`]
myObject[`attendent-${index}-name`]
and keep the pair
I have an object array containing one property call name like this
var nameArr = [
{
"name":"john"
},
{
"name":"carl"
},
{
"name":"peter"
}
]
I have a another array called ageArr and it only contain property called age
var ageArr = [
{
"age":"22"
},
{
"age":"21"
},
{
"age":"32"
}
]
i want to concat these array and end result should result like this
var result = [
{
"age":"22",
"name":"john"
},
{
"age":"21",
"name":"carl"
},
{
"age":"32",
"name":"peter"
}
]
note that length of the both arrays always equal and dynamic. Is there any way i can do this without looping these array inside one another. Thank you.
You can use Object.assign() and map() and return new array.
var nameArr = [{"name":"john"},{"name":"carl"},{"name":"peter"}]
var ageArr = [{"age":"22"},{"age":"21"},{"age":"32"}]
var result = nameArr.map(function(e, i) {
return Object.assign({}, e, ageArr[i])
})
console.log(result)
Single forEach() function is enough.
var nameArr=[{name:"john"},{name:"carl"},{name:"peter"}],
ageArr=[{age:"22"},{age:"21"},{age:"32"}];
nameArr.forEach((v,i) => v.age = ageArr[i].age)
console.log(nameArr);
You can use the following code snippet.
var result = nameArr.map(function( obj, index ) {
var res = ageArr[index];
res.name = obj.name;
return res;
});
In the map, you can easily use
jQuery.extend()
to create a merge of two same index object.
I have a data structure like the following:
obj
name
parent
name
parent
name
parent
I'm trying to do a recursive function to get the name element for as long as there is a parent element. I've come up with the following code, but it doesn't work because it converts the result to a string (and not an object). Can someone give me some advice on how to best achieve this. The nesting of parent elements is varied, not fixed (even though above I only specified 3 layers). It can range from 1 to 100.
My Desired Result
My end goal is to extract all the 'name' elements from every 'parent' down the hierarchy (and push them to an array).
function getElem(obj){
var result = '';
var parent = '.parent';
var temp = '';
if(!obj.parent){
return obj.name
}
else {
//structure is obj.parent.parent.parent...name
temp += parent;
result = obj + temp + '.name';
console.log(result);
getElem(result);
}
}
getElem(e.data);
function getElem(obj, arr) {
arr = arr || [];
if (obj.name) {
arr.push(obj.name);
}
if (obj.parent) {
return (getElem(obj.parent, arr));
}
else {
return (arr);
}
}
JSFiddle
Assuming that your data structure looks like this
var obj = {
name: 'abc',
parent: {
name: 'def',
parent: {
name: 'ghi',
parent: {
name: 'jkl'
}
}
}
};
function for loop that walks recursively for "parent" key by assigning "parent" key's value in obj and breaks the loop when the key "parent" is not available...
function get_names(obj, key) {
var names = [];
if(key == "undefined") {
key = "parent"; // default value of key
}
do {
if(typeof obj === "undefined") {
break; // breaks the loop when obj/parent is undefined
}
names.push(obj.name); // pushes all the elements named as 'name'
} while(obj = obj.parent);
return names;
}
Logs the array in console
console.log(get_names(obj));
I don't think you need recursion.
function getElem(obj) {
var names = [obj.name];
while (obj.parent) {
obj = obj.parent;
names.push(obj.name);
}
return names;
}