Find the difference inside the same object based on certain properties - javascript

I have an object where at global level I have the changed values and inside one property called initialData I have the initial value, so what I am trying to do is, based on the array mentioned values I need to find whether the initialData has been changed or not.
const eligibleFields = ['address_line_1', "is_new"]
const object = {
"id": "1",
"isGetting": false,
"address_line_1": "Washington DC",
"address_line_2": "Newyork",
"isOpen": false,
"comment": "Changed Data",
"initialData": {
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork",
},
"comment": "Initial Data"
}
}
Here first I need to loop through the mentioned fields like address_line_1 and take the value as Washington, now compare it outer not inside initialData, so outer its Washington DC, so there is a change.
Now I need to return a boolean value.
This is working, but is there a simple way?
const eligibleFields = ['address_line_2', "is_new"]
const object = {
"id": "1",
"isGetting": false,
"address_line_1": "Washington DC",
"address_line_2": "Newyork",
"isOpen": false,
"comment": "Changed Data",
"initialData": {
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork",
},
"comment": "Initial Data"
}
}
function findVal(obj, key) {
var seen = new Set,
active = [obj];
while (active.length) {
var new_active = [],
found = [];
for (var i = 0; i < active.length; i++) {
Object.keys(active[i]).forEach(function(k) {
var x = active[i][k];
if (k === key) {
found.push(x);
} else if (x && typeof x === "object" &&
!seen.has(x)) {
seen.add(x);
new_active.push(x);
}
});
}
if (found.length) return found;
active = new_active;
}
return null;
}
let isChanged = eligibleFields.some(field => {
let initialValue = findVal(object.initialData, field)?.[0]
if (initialValue) {
let changedValue = findVal(object, field)?.[0]
if (changedValue != initialValue) {
console.log("changedValue =>",changedValue, ",", "initialValue =>",initialValue)
return true
}
}
})
console.log(isChanged)

This seems simpler to me:
const eligibleFields = ['address_line_1', "is_new"]
const object = {
"id": "1",
"isGetting": false,
"address_line_1": "Washington DC",
"address_line_2": "Newyork",
"isOpen": false,
"comment": "Changed Data",
"initialData": {
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork",
},
"comment": "Initial Data"
}
}
const recursiveSearch = (obj, searchKey) => {
for (const [key, value] of Object.entries(obj)) {
if (key === searchKey && typeof value !== 'object') {
return value;
} else if (typeof value === 'object') {
return recursiveSearch(value, searchKey);
}
}
return undefined;
};
let isChanged = eligibleFields.some(field => {
if (!(field in object)) return false;
let initialValue = recursiveSearch(object.initialData, field);
let changedValue = object[field];
if (changedValue !== initialValue) {
console.log(`"${field}" was changed from ${initialValue} to ${changedValue}`)
return true
}
})
console.log(isChanged)
This assumes that only initialData has nested values, as the example provided implies. Anyways you should consider changing your JSON schema to match the initial data and the changed data.

If initialData does not have duplicate key names inside, I suggest to flaten the initialData first into an array of key value pairs so you can access it later by key names.
In below code first we are doing the conversion from this
{
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork"
},
"comment": "Initial Data"
}
to this
{
"id": 1,
"is_new": true,
"address_line_1": "Washington",
"address_line_2": "Newyork",
"comment": "Initial Data"
}
by this code
function flatenInitialData(obj, res = {}){
for(let key in obj){
if(typeof obj[key] == 'object'){
flatenInitialData(obj[key], res);
} else {
res[key] = obj[key];
}
}
return res;
}
which goes into deepest level and takes the key value and adds it into a new key and value pair array
finally we just do one for loop only for eligibleFields which loops 2 times
Please note in below example if eligibleFields has a entry such as "is_new" but "is_new" is not in the outer array it will still count it as not changed but you can change this in the condition part inside the for loop code
const eligibleFields = ['address_line_2', "is_new"]
const object = {
"id": "1",
"isGetting": false,
"address_line_1": "Washington DC",
"address_line_2": "Newyork",
"isOpen": false,
"comment": "Changed Data",
"initialData": {
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork",
},
"comment": "Initial Data"
}
}
function flatenInitialData(obj, res = {}){
for(let key in obj){
if(typeof obj[key] == 'object'){
flatenInitialData(obj[key], res);
} else {
res[key] = obj[key];
}
}
return res;
}
var flatInitialData = flatenInitialData(object["initialData"]);
function isChanged(){
var hasChanged = false;
for(var i = 0; i < eligibleFields.length; i++){
if(object[eligibleFields[i]]){
if(object[eligibleFields[i]] != flatInitialData[eligibleFields[i]]){
hasChanged = true;
}
}
}
return hasChanged;
}
console.log(isChanged())

Related

Renaming keys in an object using reduce method in JS

Following is the object in which I want to replace countryID with value, countryName with label.
In the same object I am having localLanguages Array in which I am trying to rename language with label and languageCode with value.
array -
var obj = [{
"countryID": "CON1010",
"countryName": "Poland",
"countryCode": "pl",
"localLanguages": [{
"language": "English",
"languageCode": "en"
},
{
"language": "Polish",
"languageCode": "en"
}
]
},
{
"countryID": "CON1011",
"countryName": "UK",
"countryCode": "uk",
"localLanguages": [{
"language": "English",
"languageCode": "en"
}]
}
];
Transformed to -
var obj = [{
"value": "CON1010",
"label": "Poland",
"countryCode": "pl",
"localLanguages": [{
"label": "English",
"value": "en"
},
{
"label": "Polish",
"value": "en"
}
]
},
{
"value": "CON1011",
"label": "UK",
"countryCode": "uk",
"localLanguages": [{
"label": "English",
"value": "en"
}]
}
];
Code -
arr.map(x => {
var newObj = Object.keys(x).reduce((obj, key) => {
if (key !== 'countryID') {
obj[key] = x[key]
}
if (key === 'countryID') {
obj.value = x.countryID;
}
}, {})
console.log(newObj);
return newObj;
})
Here is a solution with es6 Destructuring and map:
const arr = [{"countryID":"CON1010","countryName":"Poland","countryCode":"pl","localLanguages":[{"language":"English","languageCode":"en"},{"language":"Polish","languageCode":"en"}]},{"countryID":"CON1011","countryName":"UK","countryCode":"uk","localLanguages":[{"language":"English","languageCode":"en"}]}];
const result = arr.map(item => {
let localLanguages = item.localLanguages.map(i => {
const { language: label, languageCode: value, ...rest } = i;
return { label, value, ...rest };
});
const { countryID: value, countryName: label, ...rest } = item;
return { value, label, ...rest, localLanguages };
});
console.log(result)
Use Array.map() to convert the outer objects, and another map to convert the localLanguages:
const arr = [{"countryID":"CON1010","countryName":"Poland","countryCode":"pl","localLanguages":[{"language":"English","languageCode":"en"},{"language":"Polish","languageCode":"en"}]},{"countryID":"CON1011","countryName":"UK","countryCode":"uk","localLanguages":[{"language":"English","languageCode":"en"}]}];
const result = arr.map(o => ({
value: o.countryID,
label: o.countryName,
countryCode: o.countryCode,
localLanguages: o.localLanguages.map(l => ({
value: l.languageCode,
label: l.language
}))
}));
console.log(result)
You have forgotten to return obj value in the reduce function
var newObj = Object.keys(x).reduce( (obj, key) => {
if(key !== 'countryID') {
obj[key] = x[key]
}
if(key === 'countryID') {
obj.value = x.countryID;
}
}, {})
Here the function to change the keys. Use it with every element of you arrayrecursively, but check the type of every element
function changeKeys(obj) {
const rename = {
'countryID': 'value',
'countryName': 'label',
'language': 'label',
'languageCode': 'value'
}
return Object.keys(obj)
.reduce(
(acc, rec) => {
if (typeof rename[rec] !== 'undefined') {
return {...acc, [rename[rec]]: obj[rec]}
}
return {...acc, [rec]: obj[rec]}
}, {}
)
}

How can we flatten XML parsed with fast-xml-parser

Have inherited a Node.js app that needs some maintenance and its not my strong point.
We are parsing XML using fast-xml-parser which works really well with most of our inputs. However we have some inputs that are an extra level deep, and we need to flatten the output to all be same level.
The Input: where Price value is the extra level deep
<products capture-installed="true">
<script/>
<script/>
<script/>
<product>
<pid>8</pid>
<modelno>6273033</modelno>
<name>
<![CDATA[ Big Red Truck ]]>
</name>
<category>
<![CDATA[ Toys]]>
</category>
<currency>USD</currency>
<price>
<actualprice>19.20</actualprice>
</price>
</product>
When we flatten it with existing code we get:
"product": {
"pid": "8",
"modelno": "6273033",
"name": "Big Red Truck",
"category": "Toys",
"currency": "USD",
"price": {
"actualprice": "19.20"
}
But what we need is something like:
"product": {
"pid": "8",
"modelno": "6273033",
"name": "Big Red Truck",
"category": "Toys",
"currency": "USD",
"price-actualprice": "19.20"
}
The current Code:
const parse = require("fast-xml-parser");
const options = {
ignoreAttributes : true,
ignoreNameSpace : false,
parseNodeValue : false,
tagValueProcessor : a => {
if(Array.isArray(a)){
return a.join(',');
}
return a;
}
};
const flatten = (data) => {
return data.map(row => {
const fieldNames = Object.keys(row);
for (const fieldName of fieldNames) {
if(Array.isArray(row[fieldName])){
row[fieldName] = row[fieldName].join(',');
}
if(typeof row[fieldName] === 'object'){
row[fieldName] = JSON.stringify(row[fieldName]);
}
}
return row;
});
};
function findTheArray(o) {
if(Array.isArray(o)){
return o;
}
var result, p;
for (p in o) {
if( o.hasOwnProperty(p) && typeof o[p] === 'object' ) {
result = findTheArray(o[p]);
if(result){
return result;
}
}
}
return result;
}
module.exports = function parseData(data) {
return new Promise((resolve, reject) => {
try {
const isValid = parse.validate(data);
if (isValid === true) {
const pData = parse.parse(data, options);
const array = findTheArray(pData);
if(array){
resolve(flatten(array));
} else {
reject('Can\'t find any goodies!');
}
} else {
reject(isValid.err);
}
} catch (err) {
reject(err);
}
});
};
I've worked on the this area of the code but haven't been able to get any success:
if(typeof row[fieldName] === 'object'){
row[fieldName] = JSON.stringify(row[fieldName])
Ideas?
thanks
In the recent version of FXP, this is how you can do;
const options = {
ignoreAttributes: true,
stopNodes: [
"products.product.price"
],
tagValueProcessor: (tagName, tagValue, jPath, hasAttributes, isLeafNode) => {
if (jPath === 'products.product.price') {
return /([0-9]+\.[0-9]+)/.exec(tagValue)[1]
}
},
// preserveOrder: true,
};
const parser = new XMLParser(options);
let result = parser.parse(XMLdata);
Output
"product": {
"pid": "8",
"modelno": "6273033",
"name": "Big Red Truck",
"category": "Toys",
"currency": "USD",
"price": "19.20"
}
However, tag name can't be changed.

Calculate object property values based on user id

Got an object containing a user id for each user and prices, would like to create a new object/array for each user (no duplicates) and be able to calculate the total sum of price for each user. Tried using Object.values() with map and filter but can't get it to work properly
{
"data": {
"item1": {
"price": "20",
"user": "user1"
},
"item2": {
"price": "10",
"user": "user2"
},
"item3": {
"price": "50",
"user": "user1"
}
}
}
Output something like this:
{
"users": {
"user1": {
"totalSum": "70",
},
"user2": {
"totalSum": "10",
}
}
}
I'm thinking about using map to present the "users"-data, maybe an array would be better?
Using function reduce.
Important: The attribute price is a String, this approach uses object Number to convert that value to a numeric one.
var obj = { "data": { "item1": { "price": "20", "user": "user1" }, "item2": { "price": "10", "user": "user2" }, "item3": { "price": "50", "user": "user1" } }};
var result = Object.keys(obj.data).reduce((a, k) => {
if (a.users[obj.data[k].user]) {
a.users[obj.data[k].user].totalSum += Number(obj.data[k].price);
} else {
a.users[obj.data[k].user] = {
"totalSum": Number(obj.data[k].price)
}
}
return a;
}, {
'users': {}
});
console.log(result);
.as-console-wrapper {
max-height: 100% !important; top: 0;
}
You could leverage ```reduce, more information here
code (haven't tried this)
var data = JSON.parse(mainObj).data;
var usersWithTotalExpenditure = Object.keys(data).reduce(function(result, key) {
var currentItem = data[key];
var useName = currentItem.user;
var price = Number(currentItem.price);
if (userName in result) {
result[userName].totalSum += price;
} else {
result[userName] = {
totalSum: price
};
}
return result;
}, {});
var resultObject = {
users: usersWithTotalExpenditure
}
You can use a forEach loop. This relies on Javascripts powerful OR operator, which coerces the first half of the expression to false if the current user's price is not defined (meaning it is a user the loop hasn't encountered before)
`c is your initial object's data, output is empty object`
const c = obj.data;
var output = {};
Object.keys(c).forEach((val) => {
output[c[val]["user"]] = parseInt(output[c[val]["user"]]) + parseInt(c[val]["price"]) || parseInt(c[val]["price"]);
})

combining my objects and getting expected results

I am trying to merge my objects and get a result like below
{
"sports": {
"basketball": "kobe",
"swimming": {
},
"football": "ronaldo",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
Somewhere I am doing wrong in my logic can you help me out
but if I alternate my sportA and sportsB value I am getting expected
results...not sure what problem in my current scenario
providing my code below.
fiddle
https://jsfiddle.net/tjLk0frq/3/
var sportsA ={
"sports": {
"basketball": "kobe",
"football": "ronaldo"
}
};
var sportsB ={
"sports": {
"basketball": "",
"swimming": {
},
"football": "",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
};
function merge(sportsA, sportsB) {
for( var p in sportsB )
if( sportsA.hasOwnProperty(p) )
sportsA[p] = typeof sportsB[p] === 'object' ? merge(sportsA[p], sportsB[p]) : sportsB[p];
return sportsA;
}
merge(sportsA, sportsB );
console.log("unexpected result" + sportsA );
console.log( sportsA );
//expected
/*
{
"sports": {
"basketball": "kobe",
"swimming": {
},
"football": "ronaldo",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
*/
You can use jQuery's extend method, with deep merge enabled, to do this:
var output = $.extend(true, sportsB, sportsA)
outputs:
{
"sports": {
"basketball": "kobe",
"swimming": {},
"football": "ronaldo",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
You have mistake when you check sportsA.hasOwnProperty(p) in your case you only update properties that are in sportsA, but not add new from sportsB.
Also if sportsB[p] has falsy value you don't want to update it for that I've used (sportsB[p] || sportsA[p]).
Check this code.
var sportsA ={
"sports": {
"basketball": "kobe",
"football": "ronaldo"
}
};
var sportsB ={
"sports": {
"basketball": "",
"swimming": {
},
"football": "",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
};
function merge(sportsA, sportsB) {
for( var p in sportsB )
if( sportsA.hasOwnProperty(p) ) {
sportsA[p] = typeof sportsB[p] === 'object' ? merge(sportsA[p], sportsB[p]) : (sportsB[p] || sportsA[p]);
} else {
sportsA[p] = sportsB[p];
}
return sportsA;
}
merge(sportsA, sportsB );
console.log("unexpected result" + sportsA );
console.log( sportsA );
Here you go (pure JS):
function merge(obj1, obj2) {
var result = {};
for (var prop in obj1) {
if (typeof obj1[prop] === "object" && typeof obj2[prop] === "object")
result[prop] = merge(obj1[prop], obj2[prop]);
else
result[prop] = obj1[prop];
}
for (var prop in obj2) {
result[prop] = (result[prop]? result[prop]: obj2[prop]);
}
return result;
}
console.log(merge(sportsA, sportsB));
This returns a new object, rather than modify an existing one, however.
In the first for..in loop, we check if we need to recurse first, otherwise set the property of result.
In the second for..in loop, we check if the property was already defined or if it's empty, and set the property accordingly.
Output:
{
"sports": {
"basketball": "kobe",
"football": "ronaldo",
"swimming": {},
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
JSFiddle demo
The logic is breaking because when you only loop the property keys in one of the objects, you won't see the property keys that only exist in the other object.
You can get the root level keys of an object using Object.keys() which returns an array of the property names. Then you can merge the 2 sets of keys at same level and know all the final output properties needed
Then iterate those to get final results

Find object by id in Javascript

I have this tree:
{
"id": 0,
"parentId": null,
"name": "Comapny",
"children": [
{
"id": 1235,
"parentId": 0,
"name": "Experiences",
"children": [
{
"id": 3333,
"parentId": 154,
"name": "Lifestyle",
"children": [],
"merchants": [
{
"id": 348,
"name": "Experience Mad"
},
{
"id": 30,
"name": "Virgin Experience Days"
}
]
},
{
"id": 319291392,
"parentId": 318767104,
"name": "Other Experiences",
"children": [],
"merchants": [
{
"id": 353,
"name": "Tasterlab"
},
{
"id": 19,
"name": "Activity Superstore"
}
]
}
],
"merchants": [
{
"id": 35715,
"name": "Red Letter Days"
},
{
"id": 85,
"name": "The Hut"
}
]
}
]
}
I need to find object by id. For example if need to find an object with id:353, I have to get:
{"id": 353,"name": "Tasterlab"}
if I need to find an object with id:1235, I have to get: {"id": 1235,"name": "Experiences"}
I tried many times but given the complexity of the tree for me is hard to do what I want.
function findId(obj, id) {
if (obj.id == id) {
return obj;
}
if (obj.children) {
for (var i = 0; i < obj.children.length; i++) {
var found = findId(obj.children[i], id);
if (found) {
return found;
}
}
}
if (obj.merchants) {
for (i = 0; i < obj.merchants.length; i++) {
found = findId(obj.merchants[i], id);
if (found) {
return found;
}
}
}
return false;
}
Code is taken from: http://techslides.com/how-to-parse-and-search-json-in-javascript/
Return an array of objects according to key, value, or key and value matching
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else
//if key matches and value matches or if key matches and value is not passed (eliminating the case where key matches but passed value does not)
if (i == key && obj[i] == val || i == key && val == '') { //
objects.push(obj);
} else if (obj[i] == val && key == ''){
//only add if the object is not already in the array
if (objects.lastIndexOf(obj) == -1){
objects.push(obj);
}
}
}
return objects;
}
Return an array of values that match on a certain key
function getValues(obj, key) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getValues(obj[i], key));
} else if (i == key) {
objects.push(obj[i]);
}
}
return objects;
}
Return an array of keys that match on a certain value
function getKeys(obj, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getKeys(obj[i], val));
} else if (obj[i] == val) {
objects.push(i);
}
}
return objects;
}

Categories