Get unique values from an array of objects - javascript

[
{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}

Related

Filter an array where obj[array[#]] is nil or empty

Practising Ramda again.
So the situation is I have an obj:
const originalObj = {
foo: "bar",
std: "min",
baz: "",
key1: undefined,
key2: "exit",
key3: "val3",
key4: "",
};
And I have an array that I know beforehand:
const toCheckArray = ["baz", "key1", "key2", "key3", "key4", "key5"];
For every element in the array, I need to check whether such element (as key) exists in the obj, and whether the corresponding value is nil/empty. If such key exists, and the value is non-zero/empty, then I make an HTTP call like this to update the value:
const findKey2AndUpdateObj = async (originalObj) => {
const originalKey2 = originalObj.key2;
const key2 = await remoteHttpCall(originalKey2);
return { ...originalObj, key2: key2 };
};
For all the elements in the array, the remote HTTP call would be exactly the same, apart from the payload, of course.
My way of thing is to filter the list first, by doing the following steps:
const hasArray = filter(has(__, originalObj), toCheckArray); this I believe will check whether the element as a prop exists in the target obj;
I am trying to apply complement(anyPass([isNil, isEmpty])) to all the values of the obj and then somehow filter the corresponding key in the array;
Iterate the array? to make API calls and then update the obj.
I guess what I am thinking is not the best way of doing it. Would love to hear your ideas!
Also memorising the API call would be amazing too!
Or maybe I should flip step 1 and step 2? Filter out all the nil/empty ones from the obj and then do the has check.
I ended up doing this: filter(has(__, reject(anyPass([isEmpty, isNil]))(obj)), __)(arr). But surely there is better way.
Cheers!
Using a pipe, you make it a little more functional in the sense that you can pass the object into the pipe and output the keys, something like
pipe(reject(either(isNil, isEmpty)),keys,intersection(arr))(obj)
you could then pipe that into the api calls (with pipeWith)
I would use R.pick to get a partial object with only the requested keys, and then use R.reject to filter them further. Since the result is an object, you can use R.toPairs, and iterate the pairs to make api calls, and reconstruct the object with the new values.
const { curry, pipe, pick, reject, anyPass, isEmpty, isNil } = R;
const fn = curry((arr, obj) => pipe(
pick(arr),
reject(anyPass([isEmpty, isNil])),
)(obj));
const toCheckArray = ["baz", "key1", "key2", "key3", "key4", "key5"];
const originalObj = {"foo":"bar","std":"min","baz":"","key2":"exit","key3":"val3","key4":""};
const result = fn(toCheckArray, originalObj);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Functional Programming is not necessarily about using Ramda,
sometimes (for instance with async tasks), it might not help with readability and declarative code. I would use simple recursion here.
const asyncUpdater = async (keys, updater, data) => {
// base case
if(!keys.length) { return data };
const [head, ...tail] = keys;
const next = (d) => asyncUpdater(tail, updater, d);
const value = data[head];
// continue
if(!value) { return next(data); }
return next({
...data,
[head]: await updater(head, value)
})
};
// ===
const keys = ["baz", "key1", "key2", "key3", "key4", "key5"];
const data = {
foo: "bar",
std: "min",
baz: "",
key1: undefined,
key2: "exit",
key3: "val3",
key4: "",
};
function fakeHttp(key, value) {
return Promise.resolve(`${value}__REMOTELY_UPDATED__`);
}
asyncUpdater(keys, fakeHttp, data).then(console.log);
I hope I understood your requirements correctly. Clean, self-documenting, and as functional as vanilla Javascript allows.
const resolveProperties = curry((fn, picklist, input) =>
Promise.resolve(input)
.then(pickAll(picklist))
.then(reject(anyPass([isNil, isEmpty])))
.then(map(fn))
.then(Promise.props)
);
Usage and test-case:
import { pickAll, reject, isNil, map, anyPass, isEmpty, curry } from "ramda";
import { Promise } from "bluebird";
const mockHttp = (input) =>
new Promise((res) => setTimeout(res(`${input} resolved`), 100));
const resolveProperties = curry((fn, picklist, input) =>
Promise.resolve(input)
.then(pickAll(picklist))
.then(reject(anyPass([isNil, isEmpty])))
.then(map(fn))
.then(Promise.props)
);
test("resolveProperties", () => {
const list = ["baz", "key1", "key2", "key3", "key4", "key5"];
const input = {
foo: "bar",
std: "min",
baz: "",
key1: undefined,
key2: "exit",
key3: "val3",
key4: "",
};
return resolveProperties(mockHttp, list, input).then((result) =>
expect(result).toEqual({
key2: "exit resolved",
key3: "val3 resolved",
})
);
});

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.

NodeJS deep nested json comparison case insensitive key/value pair

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))
) )

Find the key in an object which contains a particular value in an array

I need to find the key(type:array) which contains a specific string. The actual data would look like as provided below
I tried to inverse the particular object with _.invert(). But doesnt make sense as it would convert the entire array to a string
{
"key1": ['asd','yrt','uyt'],
"key2": ['nbm','inb','oiu']
}
The response desired is that with we can fetch the key_name if an array element is provided.
ie if the input is lets 'asd' we need to be able to say that the key was key1
Take the keys and find the element with includes.
const
find = (object, value) => Object.keys(object).find(k => object[k].includes(value)),
object = { key1: ['asd','yrt','uyt'], key2: ['nbm','inb','oiu'] },
key = find(object, 'asd');
console.log(key);
How often are you going to do this? If only once, then:
data = {
"key1": ['asd','yrt','uyt'],
"key2": ['nbm','inb','oiu']
}
needle = 'oiu'
magicKey = Object.keys(data).filter(key => data[key].includes(needle))
Otherwise you're going to want to make a new dictionary that has your possible needles as keys!
const obj = { key1: ['asd','yrt','uyt'], key2: ['nbm','inb','oiu']}
const keyResult = Object.entries(obj).find(element => element[1].includes('asd'))
console.log(keyResult[0] || 'not found')
It might look like this:
let elementToFind = "asd";
let object = {
"key1": ['asd','yrt','uyt'],
"key2": ['nbm','inb','oiu']
}
let output;
for(let key in object) { // iterate over every key in object
if(object.hasOwnProperty(key)) { // check if we don't try to access one of object's prototype fields etc.
if(object[key].includes(elementToFind)) {
// check if object[key] which is array, contains element we look for
// if so, break the loop to prevent it from further searching
output = key;
break;
}
}
}
let search = 'asd'
let obj = {
"key1": ['asd','yrt','uyt'],
"key2": ['nbm','inb','oiu']
}
Object.keys(obj).find(key => obj[key].indexOf(search) > -1)
If there can be multiple such keys use filter instead of find.
One method would be to iterate over the key/value produced from Object.entries, and return the key where the string is found in the value (array).
const obj = {
key1: ['asd','yrt','uyt'],
key2: ['nbm','inb','oiu']
}
function getKey(obj, str) {
for (let [key, arr] of Object.entries(obj)) {
if (arr.includes(str)) return key;
}
return null;
}
console.log(getKey(obj, 'inb'));
console.log(getKey(obj, 'asd'));
You could write a function that recursively returns the path...
const hasKey = (key, data) => ({}).hasOwnProperty.call(data, key);
const typeOf = value => ({}).toString.call(value).replace(/\[object (\w+)]/, '$1');
const isLeaf = value => ['String', 'Boolean', 'Null', 'Undefined', 'Number', 'Date', 'Function'].includes(typeOf(value));
const needle = 'asd';
const haystack = {
"key1": ['asd1','yrt','uyt'],
"key2": ['nbm','inb','oiu'],
key3: {
key4: ['asd'],
}
};
const find = (needle, haystack, path = []) => {
if (isLeaf(haystack)) {
return needle === haystack && path;
}
for (let [key, value] of Object.entries(haystack)) {
const result = find(needle, value, path.concat(key));
if (result) { return result; }
}
};
console.log('find', find(needle, haystack));

Adding keys to a json using an array of string

I have a basic json question that is giving me headache since a couple of hours, I am trying to dynamically add keys to a Json object using an array of string.
Here is my array of string:
let key = ['session', 'view_state', 'footer', 'config', 'items']
I have another variable which is jsonValue and is my whole json object. I want to end up with one of those options:
jsonValue.session.view_state.footer.config.items
jsonValue['session']['view_state']['footer']['config']['items']
This is my best attempt using a forEach.
forEach(jsonKeys, (el) => {
jsonCollection += jsonCollection !== undefined ? '."' +[el + '"'] : [ '"' + el + '"' ];
})
But I have this result: 
undefined"session"."view_state"."footer"."config"."items"
Any help will be appreciated!
To get a value using the keys array
Iterate with Array#reduce, check the type of the current value, if it's an object, return the value of the key, if not return undefined:
const obj = {
"demo": true,
"session": {
"view_state": {
"footer": {
"config": {
"items": [
1,
2
]
}
}
}
}
};
const keys = ['session', 'view_state', 'footer', 'config', 'items'];
const value = keys.reduce((val, key) => val && typeof val === 'object' ?
val[key] : undefind, obj);
console.log(value);
To add a value using the keys array
Use Array#reduceRight to create a chain of objects with the value you want. Use Object#assign to update the original object with the results:
const keys = ['session', 'view_state', 'footer', 'config', 'items'];
const obj = { demo: true };
Object.assign(obj, keys.reduceRight((val, key) => ({ [key]: val }), [1, 2])); // replace [1, 2] with the actual items
console.log(obj);

Categories

Resources