How to find object value from property path in Javascript object - javascript

I explain the problem with sample.
For example i have javascript object. It looks like:
var trial= { points:[{x:1,y:2},{x:5,y:2},{x:3,y:4}] , obj:{id:5,name:"MyName"} }
I use deep diff module to find difference between two json array. Then it find the differences and find difference path. If value of x is changed, then it finds.
For example
path = ["points",0,"x"]
or
path= ["obj","name"]
So my question is that how to generate json object from these path.
For example i have to generate that
trial.points[0].x or trial.obj.name
How to do that ? thank you for your answer.

You can use the following logic:
for(var i = 0, result = trial; i < path.length; i++) {
result = result[path[i]];
}

You can use array#reduce and pass your object and path as variables. Array#reduce will return the value corresponding to a path.
var trial= { points:[{x:1,y:2},{x:5,y:2},{x:3,y:4}] , obj:{id:5,name:"MyName"} },
path1 = ["points",0,"x"],
path2= ["obj","name"],
valueAtPath = (object, path) => path.reduce((r,p) => r[p], object);
console.log(valueAtPath(trial, path1));
console.log(valueAtPath(trial, path2));

You can do like this:
var trial= { points:[{x:1,y:2}, {x:5,y:2},{x:3,y:4}] , obj:{id:5,name:"MyName"}};
var path = ["points", 0, "x"];
var object = trial;
path.map(field => object = object[field]);
console.log(object);
path = ["obj", "name"];
var object = trial;
path.map(field => object = object[field]);
console.log(object);

Disclosure: I am the author of both deep-diff and json-ptr.
To build an object out of an array of attribute names such as [ "points", 0, "x" ], use a good JSON Pointer implementation, since most of them have convenience methods for translating these paths, and applying values to object graphs.
For example (Node.js):
const diff = require("deep-diff");
const ptr = require("json-ptr");
let original = {
points: [
{ x: 1, y: 2 },
{ x: 5, y: 2 },
{ x: 3, y: 4 }
],
obj: {
id: 5,
name: "MyName"
}
};
let modified = JSON.parse(JSON.stringify(original));
modified.points[0].x = 7;
modified.obj.name = "Wilbur Finkle";
const differences = diff(original, modified);
// Produce an object that represents the delta between original and modified objects
const delta = differences.reduce((acc, record) => {
// Only process edits and newly added values
if (record.kind === "E" || record.kind === "N") {
ptr.set(
acc, // target
ptr.encodePointer(record.path), // pointer; from path
record.rhs, // modified value
true // force; creates object graph
);
}
return acc;
}, {});
console.log(JSON.stringify(delta, null, " "));
Produces:
{ points: [ { x: 7 } ], obj: { name: "Wilbur Finkle" } }

Related

JS React: Two Objects, change properties if name matches

i have a problem with Objects in JavaScript (React).
I have two different Objects, which are generated from two different XML-Files. Each Object has the same Names in it but the points and position can be different. My Goal is to add the Points from the second Object to the First if the name matches.
The Structure of the Objects is the following:
let obj = [{name: "Max", points: 2},{name:"Marc", points: 1}]
let obj2 = [{name:"Marc", points: 2},{name: "Max", points: 1}]
The Goal is to have one updated Object:
let updatedObj = [{name: "Max", points:3},{name:"Marc", points:3}]
My Code looks like this rn:
import React, {useState} from 'react'
import axios from 'axios'
const App = () => {
const [list1,setList1] = useState()
const [list2,setList2] = useState()
const readFile = (file, number) => {
const dayPath = `/files/`;
axios.post(`http://localhost:5001/getData`, {dayPath, file})
.then(res => {
// File Number 1
if(number === 1){
let obj = res.data.vehicles[1].vehicle.map((item) => (
{
name: item.name,
points: res.data.vehicles[0] - Number(item.Position) +1 // Vehicles Total - Position + 1
})
)
setList(obj)
}else {
/*
* Get second Object -> Map -> Find matching Name ->
* Keep the Name Value -> (Calculate) and Add Points ->
* Push to State
*/
}
})}
}
return (
<div>App</div>
)
export default App
I've tried it with Object.entries, but only the last item was updated and the others were empty.
Thanks for your help!
let obj = [{name: "Max", points: 2},{name:"Marc", points: 1}]
let obj2 = [{name:"Marc", points: 2},{name: "Max", points: 1}]
const updatedObj = obj.map((person)=> {
const personRes = obj2.find((searchPerson)=> searchPerson.name === person.name)
if(personRes){
person.point += personRes.point
}
return person
})
You could use this createCombinedArray function. It is a little longer than some solutions, but the function is very easy to read, flexible (even with inconsistent or incorrect inputs), and has a runtime complexity of O(n) (A.K.A. a linear runtime complexity):
let obj = [{name: "Max", points: 2},{name:"Marc", points: 1}]
let obj2 = [{name:"Marc", points: 2},{name: "Max", points: 1}]
function createCombinedArray(arr1, arr2) {
//we'll keep track of how many points are associated with a name
let pointTracker = {}
//total all the points for every name in arr1
for (let i = 0; i < arr1.length; i++) {
let currObj = arr1[i]
//if this object has a 'name' property and a property 'number' that
//is actually a number
if (currObj.hasOwnProperty('name') && !isNaN(currObj.points)) {
//then add it to the name's total if it exists
if (pointTracker[currObj.name] !== undefined) {
pointTracker[currObj.name] += +currObj.points
} else {
//or if it doesn't exist, then make a new entry in your point
//tracker system with those points
pointTracker[currObj.name] = +currObj.points
}
}
}
//in addtion, total all the points for every name in arr2, also
//in the pointTracker object
for (let i = 0; i < arr2.length; i++) {
let currObj = arr2[i]
//if this object has a 'name' property and a property 'number' that
//is actually a number
if (currObj.hasOwnProperty('name') && !isNaN(currObj.points)) {
//then add it to the name's total if it exists
if (pointTracker[currObj.name] !== undefined) {
pointTracker[currObj.name] += +currObj.points
} else {
//or if it doesn't exist, then make a new entry in your point
//tracker system with those points
pointTracker[currObj.name] = +currObj.points
}
}
}
let combinedArr = []
//now fill in the currently-empty return array, by making and
//pushing objects for all the names found in our pointTracker object
for (let name in pointTracker) {
combinedArr.push({name: name, points: pointTracker[name]})
}
return combinedArr
}
//here's the function in action
const newObj = createCombinedArray(obj, obj2)
console.log(newObj)
This code will print out the array
[ { name: 'Max', points: 3 }, { name: 'Marc', points: 3 } ]
Just include the function code anywhere in the same .js file in your React project, and use the function where ever you like in that file.
One possible downside to this solution is that if the objects in your original arrays contained any properties that weren't name or points, then those properties' values won't be inside of the new object.

How to normalize JS Object with special char property

Hey guys I'm working on a project and I'm getting an object that looks like this:
{
"userChoice[language]": "en",
"userChoice[responses][favColor]": "black",
"userChoice[responses][favCity]": "new york",
}
How do I normalize that? So I can access the properties that I need?
Thanks
Here is a es5 approach to normalize the object
function normalisedObject(object) {
var normalised = {};
for (var key in object) {
//regex to capture all [.*] groups
var matches = key.match(/\[(.*?)\]/g);
if (matches) {
var temp = normalised;
while (matches.length > 0) {
//get first key and replace []
var newKey = matches[0].replace(/\[|\]/g, "");
if (matches.length !== 1) {
//keep traverse if the left over keys are greater than 1
temp = temp[newKey] || (temp[newKey] = {}); //assign empty object
} else {
temp[newKey] = object[key];
}
//poll
matches.shift();
}
}
}
return normalised;
}
//example
const t1 = performance.now();
var object = {
"userChoice[language]": "en",
"userChoice[responses][favColor]": "black",
"userChoice[responses][favCity]": "new york"
};
var normalised = normalisedObject(object);
const t2 = performance.now();
console.log(`Operation took ${t2 - t1}ms`);
console.log(normalised);
When an object's keys don't allow you to use simple dot notation, ie obj.property, you can use square-bracket notation instead, eg
const lang = obj["userChoice[language]"]
But I have a feeling you'd actually like to transform that object into something resembling this
{
userChoice: {
language: "en",
responses: {
favColor: "black",
favCity: "new york"
}
}
}
If that's the case, you need to reduce the object entries (key / value pairs) to a new object, parsing out the key paths and building new, inner objects as you go
const obj = {
"userChoice[language]": "en",
"userChoice[responses][favColor]": "black",
"userChoice[responses][favCity]": "new york",
}
const t1 = performance.now()
const normalised = Object.entries(obj).reduce((c, [ key, val ]) => {
// get the key parts as a path array
const path = key.replace(/]/g, "").split(/\[/)
// pop the last property for assigning the value later
const prop = path.pop()
// determine the inner-most object by path
const inner = path.reduce((o, k) => {
// create a new object if it doesn't exist
return o[k] ?? (o[k] = {})
}, c)
// assign the value
inner[prop] = val
return c
}, {})
const t2 = performance.now()
console.info(normalised)
console.log(`Operation took ${t2 - t1}ms`)
.as-console-wrapper { max-height: 100% !important; }
Note that if you start throwing any array properties in, eg userChoice[foo][0], this won't work.

Is it possible to do conditional destructuring or have a fallback?

I have an object that has lots of deeply nested properties. I want to be able to access properties on "MY_KEY" (below), but if that doesn't exist then get "MY_OTHER_KEY". How can I accomplish that?
const {
X: {
Y: {
MY_KEY: {
Values: segments = []
} = {}
} = {}
} = {}
} = segment;
You could achieve this using a temporal variable inside your destructuring assignment, something like this:
function destructure(segments) {
const {
X: {
Y: {
MY_OTHER_KEY: _fallback_value = {},
MY_KEY: {
Values: segment = []
} = _fallback_value,
} = {},
} = {},
} = segments;
return segment;
}
console.log(destructure({})); // []
console.log(destructure({X:{}})); // []
console.log(destructure({X:{Y:{MY_KEY:{Values:"A"}}}})); // A
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"B"}}}})); // B
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"C"}, MY_KEY:{Values:"D"}}}})); // D
First of all, this kind of destructuring will attempt to extract the second key all the time, which might have some unintended implications, like a property getter for MY_OTHER_KEY will always run.
However I fail to see the beauty in it. Hiding some control flow inside destructuring is just confusing. I would rather suggest extracting the parent object and use regular property access on it:
function destructure(segments) {
const {
X: {
Y: nested = {},
} = {},
} = segments;
const selected = nested.MY_KEY || nested.MY_OTHER_KEY || {};
const {
Values: segment = []
} = selected;
return segment;
}
console.log(destructure({})); // []
console.log(destructure({X:{}})); // []
console.log(destructure({X:{Y:{MY_KEY:{Values:"A"}}}})); // A
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"B"}}}})); // B
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"C"}, MY_KEY:{Values:"D"}}}})); // D
While object destructuring is cool and new, it is not the best in all cases. May do:
try {
var segments = segment.X.Y.MY_KEY.Values;
} catch(e){
//whatever
}
Check if the target is defined
let {/* do destructuring stuff */}
if (MY_KEY === undefined) {
// define `MY_OTHER_KEY` variable here
}
If you are trying to assign target to a different property if the previous target definition is undefined you can assign the variable at subsequent target definition
const segment = {Y:123};
let or = "Y";
let X;
({X, X = X || segment[or]} = segment);
console.log(X);

Convert set of object's into array item

I have several objects like this:
{'id[0]': 2}
{'url[0]': 11}
{'id[1]': 3}
{'url[1]': 14}
And I want to get something like this:
[{id:2, url:11}, {id:3, url:14}]
Also I have lodash in my project. Maybe lodash have some method for this?
You could use a regular expression for the keys and create a new object if necessary. Then assign the value to the key.
var data = [{ 'id[0]': 2 }, { 'url[0]': 11 }, { 'id[1]': 3 }, { 'url[1]': 14 }],
result = [];
data.forEach(function (a) {
Object.keys(a).forEach(function (k) {
var keys = k.match(/^([^\[]+)\[(\d+)\]$/);
if (keys.length === 3) {
result[keys[2]] = result[keys[2]] || {};
result[keys[2]][keys[1]] = a[k];
}
});
});
console.log(result);
This is an ES6 solution based on #NinaScholz solution.
I assume that the objects have only one property each, like the ones presented in the question.
Combine the array of objects to one large object using Object#assign, and convert to entries with Object.entries.
Iterate the array using Array#reduce.
Extract the original key an value from each entry using array
destructuring.
Extract the wanted key and index using a regex and array
destructuring.
Then create/update the new object at the index using object spread.
const data = [{ 'id[0]': 2 }, { 'url[0]': 11 }, { 'id[1]': 3 }, { 'url[1]': 14 }];
// combine to one object, and convert to entries
const result = Object.entries(Object.assign({}, ...data))
// extract the original key and value
.reduce((r, [k, value]) => {
// extract the key and index while ignoring the full match
const [, key, index] = k.match(/^([^\[]+)\[(\d+)\]$/);
// create/update the object at the index
r[index] = {...(r[index] || {}), [key]: value };
return r;
}, []);
console.log(result);
var arr = [{'id[0]': 2},
{'url[0]': 11},
{'id[1]': 3},
{'url[1]': 14}];
var result = [];
arr.forEach(function(e, i, a){
var index = +Object.keys(e)[0].split('[')[1].split(']')[0];//get the number inside []
result[index] = result[index] || {}; //if item is undefined make it empty object
result[index][Object.keys(e)[0].split('[')[0]] = e[Object.keys(e)[0]];//add item to object
})
console.log(result);
You can use for loop, .filter(), RegExp constructor with parameter "\["+i+"\]" where i is current index, Object.keys(), .reduce(), .replace() with RegExp /\[\d+\]/
var obj = [{
"id[0]": 2
}, {
"url[0]": 11
}, {
"id[1]": 3
}, {
"url[1]": 14
}];
var res = [];
for (var i = 0; i < obj.length / 2; i++) {
res[i] = obj.filter(function(o) {
return new RegExp("\[" + i + "\]").test(Object.keys(o))
})
.reduce(function(obj, o) {
var key = Object.keys(o).pop();
obj[key.replace(/\[\d+\]/, "")] = o[key];
return obj
}, {})
}
console.log(res);

JavaScript - find unique values in array of objects, with count and create a new array of objects

I have an array of objects as below:
var arr =[
{
price:20,
rule:a
},
{
price:10,
rule:b
},
{
price:5,
rule:a
},
{
price:50,
rule:b
},
{
price:70,
rule:b
}
]
I want to extract an array of objects out of this as below:
var filteredArr = [
{
rule:a,
countOfRule:2,
minPriceForThisRule:5
},
{
rule:b,
countOfRule:3,
minPriceForThisRule:10
}
]
This means:
1) I want to create new array with no. of objects as unique no. of rules in first array "arr"
2) Need to count the unique rule repetition and add as property in new array objects - given as "countOfRule"
3) Find the minimum price for in a category of unique rule - given as "minPriceForThisRule"
I have read similar answers on SO and was able to get first 2 conditions only, and that too were not in the format as i need.
What I tried, referring to links on SO:
var ff = {},e;
for (var i = 0,l=arr.length; i < l; i++) {
e = arr[i];
ff[e.rule] = (ff[e.rule] || 0) + 1;
}
But this gives only a single object as
{
a : 2,
b: 3
}
You can do this with forEach and thisArg optional parameter.
var arr = [{"price":20,"rule":"a"},{"price":10,"rule":"b"},{"price":5,"rule":"a"},{"price":50,"rule":"b"},{"price":70,"rule":"b"}],
r = [];
arr.forEach(function(e) {
if(!this[e.rule]) {
this[e.rule] = {rule: e.rule, countOfRule: 0, minPriceForThisRule: e.price}
r.push(this[e.rule]);
}
this[e.rule].countOfRule++;
if(this[e.rule].minPriceForThisRule > e.price) this[e.rule].minPriceForThisRule = e.price;
}, {});
console.log(r)
I would use reduce. Something like:
var reduced = arr.reduce(function(memo, obj){
var rule = memo[obj.rule];
rule.countOfRule++;
if(!rule.minPriceForThisRule){
rule.minPriceForThisRule = obj.price;
} else{
if(obj.price < rule.minPriceForThisRule){
rule.minPriceForThisRule = obj.price;
}
}
return memo;
}, map);
where the initial map looks like:
var map = {
1: {
rule: 1,
countOfRule: 0,
minPriceForThisRule: null
},
2: {
rule: 2,
countOfRule: 0,
minPriceForThisRule: null
}
}
Of course you could dynamically create the map if needed.
https://plnkr.co/edit/wLw3tEx2SMXmYE7yOEHg?p=preview
This is how i would do this job,
var arr =[
{
price:20,
rule:"a"
},
{
price:10,
rule:"b"
},
{
price:5,
rule:"a"
},
{
price:50,
rule:"b"
},
{
price:70,
rule:"b"
}
],
reduced = arr.reduce((p,c) => { var fo = p.find(f => f.rule == c.rule);
fo ? (fo.countOfRule++,
fo.minPriceForThisRule > c.price && (fo.minPriceForThisRule = c.price))
: p.push({rule:c.rule, countOfRule:1, minPriceForThisRule: c.price});
return p;
},[]);
console.log(reduced);
Arrows might not work at IE or Safari. If that would be a problem please replace them with their conventional counterparts.

Categories

Resources