NodeJS deep nested json comparison case insensitive key/value pair - javascript

I want to compare two nested json in NodeJS, json key position can change and also key/value comparison should bee case insensitive
I'm using deep-equal NodeJS module, but it's only working for case sensitive comparison
let json1 = {
"type": "NEW",
"users": [{
"id": "dskd3782shdsui",
"email": "helloworld#xxxxxx.com"
}],
"ordered": [{
"productId": "SHFDHS37463",
"SKU": "ffgh"
}]
}
let json2 = {
"type": "NEW",
"users": [{
"id": "dskd3782shdsui",
"email": "helloworld#xxxxxx.com"
}],
"ordered": [{
"productId": "SHFDHS37463",
"SKU": "ffgh"
}]
}
var deepEqual = require('deep-equal')
console.log('==', deepEqual(
json1,
json2
))
Above code is working but if I change json2 email to helloworld#xxxxxx.COM or email key to EMAIL it's returning false I want case insensitive comparison.

To handle case insensitive comparisons between string values (this won't work for keys), you can use lodash's _.isEqualWith() with a customizer function:
const obj1 = {"type":"NEW","users":[{"id":"dskd3782shdsui","email":"helloworld#xxxxxx.COM"}],"ordered":[{"productId":"SHFDHS37463","SKU":"ffgh"}]}
const obj2 = {"type":"new","users":[{"id":"dskd3782shdsui","email":"helloworld#xxxxxx.com"}],"ordered":[{"productId":"SHFDHS37463","SKU":"ffgh"}]}
const result = _.isEqualWith(
obj1,
obj2,
(objValue, othValue) => _.isString(objValue) && _.isString(othValue) ?
objValue.toLowerCase() === othValue.toLowerCase()
:
undefined
)
console.log('==', result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
To handle keys with different cases, you should normalize the keys to lower case, and then make the comparison. I've use _.transform() to create a recursive function that iterates a nested object, and converts all keys to lower case.
const normaliseKeys = obj => _.transform((r, k, v) => {
const key = _.isString(k) ? k.toLowerCase() : k;
r[key] = _.isObject(v) ? normaliseKeys(v) : v;
})
const obj1 = normaliseKeys({"TYPE":"NEW","users":[{"id":"dskd3782shdsui","EMAIL":"helloworld#xxxxxx.COM"}],"ordered":[{"productId":"SHFDHS37463","SKU":"ffgh"}]})
const obj2 = normaliseKeys({"type":"new","users":[{"id":"dskd3782shdsui","email":"helloworld#xxxxxx.com"}],"ordered":[{"productId":"SHFDHS37463","SKU":"ffgh"}]})
const result = _.isEqualWith(
obj1,
obj2,
(objValue, othValue) => _.isString(objValue) && _.isString(othValue) ?
objValue.toLowerCase() === othValue.toLowerCase()
:
undefined
)
console.log('==', result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

If u don't mind using https://lodash.com/docs/4.17.4#isEqualWith
var result = _.isEqualWith(json1, json2, (value1, value2, key) => {
console.log(value1, value2);
//check for string and compare
});

You can use:
function jsonEqual(a,b) {
return JSON.stringify(a).toLowerCase() === JSON.stringify(b).toLowerCase();
}
console.log(jsonEqual(json1, json2))
Working example here.

I got solution for this it will work for both key/value case insensitive and also if we change key position
var deepEqual = require('deep-equal')
function toLower(a) {
return JSON.stringify(a).toLowerCase();
}
console.log('==', deepEqual(
JSON.parse(toLower(json1)),
JSON.parse(toLower(json2))
) )

Related

Join elements of an object and exclude some

I have a Typescript project where I want to join all the values of an Object except one.
This is my Object:
let dataInit = {
"host": "CAPR",
"ua": "RMA",
"country": "VE",
"page":3
};
This is what I do:
let dataJoin = Object.values(dataInit).join(',')
This is what I get:
CAPR,RMA,VE,3
I need to know how to remove the 'page' property, this is what I want:
CAPR,RMA,VE
If you don't know the other attributes, you can first use Object.entries() to give you an array of arrays containing the keys and values. That array can then be filtered to remove the "page" element, mapped to just contain the value, and finally joined.
let dataInit = {
"host": "CAPR",
"ua": "RMA",
"country": "VE",
"page":3
};
console.log(
Object.entries(dataInit)
.filter(([key,val]) => key !== "page")
.map(([_,val]) => val)
.join(",")
)
I would destructure the object first and create a new one
const { host, ua, country } = dataInit
const dataNew = { host, ua, country }
And then call the values join method on the new object.
You could filter the array resulted:
let dataJoin = Object.values(dataInit).filter(element => typeof element === "string").join(",");
Or you can use destructuring as presented in the other comments.
You can use for...in loop to iterate over element and skip page property.
let dataInit = {
"host": "CAPR",
"ua": "RMA",
"country": "VE",
"page": 3
};
const convertObject = (obj) => {
let list = [];
for (const prop in obj) {
if (prop !== "page") {
list.push(obj[prop]);
}
}
return list.join(",");
}
console.log(convertObject(dataInit));

Get unique values from an array of objects

[
{key1 : 'sometext'},
{key2 : 'sometext'},
{key1 : 'sometext'},
{key3 : 'sometext'},
]
From the above code I need to get the results as follows, removing objects that contains same keys
[
{key1 : 'sometext'},
{key2 : 'sometext'},
{key3 : 'sometext'},
]
Thanks in advance.
With lodash you can use _.uniqBy(), and return the single key of each object:
const arr = [{"key1":"sometext"},{"key2":"sometext"},{"key1":"sometext"},{"key3":"sometext"}]
const result = _.uniqBy(arr, o => _.keys(o)[0])
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Using vanilla JS, if you don't care if the 2nd item (the duplicate would be used, you can combine all items to a single object (this will remove the 1st duplicate), get the entries, and map back to an array of objects:
const arr = [{"key1":"sometext"},{"key2":"sometext"},{"key1":"sometext"},{"key3":"sometext"}]
const result = Object.entries(Object.assign({}, ...arr))
.map(([k, v]) => ({ [k]: v }))
console.log(result)
_.uniqBy(data, _.findKey);
Explanation:
In _.uniqueBy() you need to tell on what value you want to derive the uniqueness, either you pass a string as attribute (the value of that attribute will be used in to determine uniqueness), or you pass a callback which will return a value and that value will be considered to evaluate uniqueness. In case the value upon which we will determine the uniqueness is a plain and native value, _.uniqueBy() is a better choice, so you don't need to compare manually (which you have to do if your value is a complex object and upon many keys the uniqueness is determined). In our case it is simple object key, which can be either string or symbol, So, the caparison part we can let on lodash with _uniqueBy
In our case, if we can return the only key (it must be exactly 1, otherwise none of the logic will work) of each object, _.uniqueBy() can do the further, so basically our aim is to pass a callback which will return the only key. we can simply pass a callback to evaluate that like: e => _.keys(e)[0]
Now, _.findkey takes object as first argument and truthy-ness of the returned value, if it's a truthy then it will return that key (unlike return that entity like _.find), so if you don't pass the callback, a default callback valued a => a is automatically assigned to handle that case. which means it will check the truthy ness of each entity (key in this case), and obviously, key have to be a truthy value, so it will return the first entity itself (first key here for _.findKey), hence passing a callback wrapping findKey with only one argument like a => _.findKey(a) is equivalent to pass _.findKey callback, we can make it more simple and _.uniqueBy(data, _.findKey) will do the job.
Let's have a snippet:
let data=[{key1:"sometext"},{key2:"sometext"},{key1:"sometext"},{key3:"sometext"}];
let res = _.uniqBy(data, _.findKey);
console.log('Unique Result: ', res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
This is using pure javascript, not using loadash
const arr = [
{key1 : 'sometext'},
{key2 : 'sometext'},
{key1 : 'sometext'},
{key3 : 'sometext'},
];
const result = arr.reduce((acc, item) => {
if (!acc.find(x => Object.keys(x).sort().toString() == Object.keys(item).sort().toString() )) {
acc.push(item)
}
return acc;
}, []);
console.log(result);
Simple and fast solution. Not much iteration.
const data = [
{ key1: "sometext" },
{ key2: "sometext" },
{ key1: "sometext" },
{ key3: "sometext" }
];
const uniqueElementsBy = (arr, fn) =>
arr.reduce((acc, v) => {
if (!acc.some(x => fn(v, x))) acc.push(v);
return acc;
}, []);
const isEqual = (x, y) => JSON.stringify(x) == JSON.stringify(y);
console.log(uniqueElementsBy(data, isEqual));
// For complex solution
const isEqual2 = (x, y) => {
const keys1 = Object.keys(x);
const keys2 = Object.keys(y);
if (keys1.length != keys2.length) return false;
return !keys1.some(key => x[key] != y[key]);
};
console.log(uniqueElementsBy(data, isEqual2));
const data2 = [
{ key2: "sometext", key1: "sometext" },
{ key2: "sometext" },
{ key1: "sometext", key2: "sometext" },
{ key3: "sometext" }
];
console.log(uniqueElementsBy(data2, isEqual2));
const data3 = [
{ key1: "sometext" },
{ key2: "sometext" },
{ key1: "sometext", key2: "sometext" },
{ key3: "sometext" }
];
console.log(uniqueElementsBy(data3, isEqual2));
.as-console-row {color: blue!important}

Get nested objects key as joined string

Input:
{
"mobile": "Mob # Required",
"client": [
undefined,
null,
{
"usergroup": "Required"
},
{
"id": "Required",
"usergroup": "Required"
},
{
"id": "Required",
"usergroup": "Required"
}
]
}
Expected Output:
[
"mobile",
"client.2.usergroup",
"client.3.id",
"client.3.usergroup",
"client.4.id",
"client.4.usergroup"
]
I am using Formiks FieldArray in my project & the field name in error object is not what is expected.
Object.Keys() doesn't work well for such scenario.
You can flatMap the keys of the object. If the current key's value is an object, recursively call getKeys function with the updated prefix. If not, return the current key with the given provided prefix. Use flatMap to get a flattened array of keys instead of nested arrays
const input={mobile:"Mob # Required",client:[{usergroup:"Required"},{id:"Required",usergroup:"Required"},{id:"Required",usergroup:"Required"}]};
const getKeys = (o, prefix = '') =>
Object.keys(o).flatMap(k =>
Object(o[k]) === o[k] ? getKeys(o[k], `${prefix}${k}.`) : [prefix + k]
)
console.log(getKeys(input))
If flatMap is not supported, you can reduce the keys of the object with similar logic
const input={mobile:"Mob # Required",client:[{usergroup:"Required"},{id:"Required",usergroup:"Required"},{id:"Required",usergroup:"Required"}]};
function getKeys(o, prefix = '') {
return Object.keys(o).reduce((acc, k) => {
if (Object(o[k]) === o[k])
acc.push(...getKeys(o[k], `${prefix}${k}.`))
else
acc.push(prefix + k)
return acc;
}, [])
}
console.log(getKeys(input))
I'm sure there are libraries out there to do this for you, but here is what I came up with:
function flattenNestedObject(input, path) {
path = path || [];
return Object.keys(input).reduce(function(arr, key) {
if (input[key] && typeof input[key] === "object") {
return arr.concat(flattenNestedObject(input[key], path.concat(key)));
}
if (typeof input[key] !== 'undefined' && input[key] !== null) {
return arr.concat((path.concat(key).join(".")));
}
return arr;
}, []);
}
https://codesandbox.io/s/sweet-jang-j51dh
The reason Object.keys will not work for you is that it doesn't recursively obtain all keys of an object. Also, in your expected output, you don't want all the keys of an object if it contains nested Arrays or Objects.

How to map items without undefined value in javascript with Array.prototype.map and without Array.prototype.filter()

How to map items without undefined value in javascript with Array.prototype.map and without Array.prototype.filter()
Code:
var testArray = [
{
name: "toto",
totoNumber: "1",
},
{
name: "tata",
totoNumber: "2",
},
{
mame: "error",
},
]
var listOfToto = testArray.map(x => x.totoNumber);
var listOfToto2 = testArray.map(x => x.totoNumber ? x.totoNumber : undefined);
var listOfToto3 = testArray.filter(x => x.totoNumber).map(x => x.totoNumber);
console.log(listOfToto);
console.log(listOfToto2);
console.log(listOfToto3);
Values:
Array ["1", "2", undefined]
Array ["1", "2", undefined]
Array ["1", "2"]
I want to get the last array (["1", "2"]) without using Array.prototype.filter()
(maybe with something else than Array.prototype.map())
Other source:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Why does javascript map function return undefined?
https://codewithhugo.com/avoiding-falsy-values-in-javascript-arrays/
You can use Array.flatMap() and return an empty array [] if the value doesn't exist:
const testArray = [{"name":"toto","totoNumber":"1"},{"name":"tata","totoNumber":"2"},{"mame":"error"}]
const listOfToto2 = testArray.flatMap(x => x.totoNumber ? x.totoNumber : []);
console.log(listOfToto2);
You can use reduce.
let data = testArray.reduce((result, item) => item.totoNumber ? result.concat(item.totoNumber) : result, []);
// Or a slightly more efficient solution
// if you are worried about concat creating a new temporary array at each iteration
let data = testArray.reduce((result, item) => {
if (item.totoNumber) {
result.push(item.totoNumber);
}
return result;
}, []);
But why do you want to avoid filter? It makes the code much more readable (in my opinion), and unless you are dealing with a huge array or working in a very time-sensitive function, this will be a micro-optimization for which you should not sacrifice clarity.
This looks much cleaner in my opinion:
let data = testArray.map(item => item.totoNumber).filter(e => e);
Here are few options you can consider all which require one loop.
var arr = [ { name: "toto", totoNumber: "1", }, { name: "tata", totoNumber: "2", }, { mame: "error", }, ]
// via Array.forEach
let fe = []
arr.forEach(({totoNumber}) => totoNumber ? fe.push(totoNumber) : 0)
// via Array.reduce
let r = arr.reduce((acc,{totoNumber}) => (totoNumber ? acc.push(totoNumber) : 0, acc), [])
// via Array.map acting like Array.forEach
let m = []
arr.map(({totoNumber}) => (totoNumber ? m.push(totoNumber) : 0, totoNumber))
console.log(fe)
console.log(r)
console.log(m)
In reality only the Array.reduce make sense and the usual way to handle your scenario would be via Array.filter and then Array.map but since you asked if you can do it with map only ... you could but map is supposed to return the exact same array length so it is not the best fit really.

Set First Value as Key in Javascript Array

Creating an array based off selected DataTables Rows
$('#savenlp').click(recordjourney);
function recordjourney() {
var data = table.rows(['.selected']).data().toArray();
console.log( (data) );
console.log( JSON.stringify(data) );
}
data returns
0 : (8) ["Which", "TitleCase", "QuestionWord", "", "", "", "", ""]
JSON.stringify(data) returns
[["baseball","Noun","Singular","","","","",""]]
This information is dynamically generated, so I am just looking to take the first value (in this case baseball) and turn it into something like
"baseball": [
"Noun",
"Singular"
]
I can return the first value (the key I want using)
alert(data[0][0]);
I am much more adept in PHP but I am learning javascript/jquery more and more.
It is my understanding javascript does not have associative arrays, so I am a bit confused as to how to generate this.
const data = [
["baseball","Noun","Singular","","","","",""],
["baseballs","Noun","","Plural","","","","",]
];
const mappedData = data.reduce((acc, row) => { acc[row.shift()] = row.filter(d => d !== ''); return acc; }, {});
console.log(mappedData);
We can use object destructuring and spread operators for ease of use.
In the example below, the key will be the first item and all the rest items will be placed in the newData variable
const data = [["baseball","Noun","Singular","","","","",""]];
const [key, ...newData] = data[0]
// if you want the new data to not have empty entries, simple apply the filter
const newDataFiltered = newData.filter(item => !!item)
const objWithEmpty = {[key]: newData}
const objWithoutEmpty = {[key]: newDataFiltered}
console.log(objWithEmpty, objWithoutEmpty)
For multiple arrays inside the outer array, just enclose the whole logic inside a for loop
const data = [
["baseball","Noun","Singular","","","","",""],
["baseball1","Noun1","Singular1","","","","",""],
["baseball2","Noun2","Singular2","","","","",""]
];
const objWithEmpty = {}
const objWithoutEmpty = {}
data.forEach((array) => {
const [key, ...newData] = array
// if you want the new data to not have empty entries, simple apply the filter
const newDataFiltered = newData.filter(item => !!item)
objWithEmpty[key] = newData
objWithoutEmpty[key] = newDataFiltered
})
console.log(objWithEmpty, objWithoutEmpty)
Simply extract the desired values from data and put them into an object formatted as you like:
const data = [["baseball","Noun","Singular","","","","",""]];
const firstArr = data[0];
const transformedFirstObject = {
[firstArr[0]]: [firstArr[1], firstArr[2]],
};
console.log(transformedFirstObject);
But it's pretty weird to have an object with only one property like that. If your data might have more than one sub-array in it and you want to turn the array of arrays into an array of objects, use map:
const data = [
["baseball","Noun","Singular","","","","",""],
["foo","bar","baz","","","","",""]
];
const transformed = Object.assign(...data.map(([prop, value1, value2]) => ({ [prop]: [value1, value2] })));
console.log(transformed);
A bit simpler compared to other answers here but works as well.
const data = [
["baseball","Noun","Singular","","","","",""],
["baseball1","Noun1","Singular1","","","","",""],
["baseball2","Noun2","Singular2","","","","",""]
];
const obj = [];
data.forEach(function(i) {
let jsonObj = {};
jsonObj [i[0]] = i.slice(1).filter(x=>x !='');
obj.push(jsonObj)
});
console.log(JSON.stringify(obj))
Just using forEach, considering multiple array elements.
var obj = {};
var arr = [
["baseball", "Noun", "Singular", "", "", "", "", ""],
["Test", "Test1", "Test2", "", "", "", "", ""]
];
arr.forEach(function(val, idx) {
val.forEach(function(val1, idx1) {
if (idx1 === 0) {
obj[val1] = val.slice(1, val.length)
}
})
})
console.log(JSON.stringify(obj))

Categories

Resources