Trying to swap key value pairs of an object. Any advices? - javascript

Trying to swap key-value pairs of an object!
// an object through we have to iterate and swap the key value pairs
const product = {
id: "FRT34495",
price: 34.56,
nr: 34456,
};
// A function that actually swap them, but don't delete old properties
const change = () => {
for (let key in product) {
const x = key;
key = product[key];
product[key] = x;
}
return product;
};
console.log(change());
//
{
'34456': 'nr',
id: 'FRT34495',
price: 34.56,
nr: 34456,
FRT34495: 'id',
'34.56': 'price'
}
the problem is that I need the object with key-value pairs swapped, but in the same amount, not twice more as we can see above, I need to delete the old ones.
Any advice guys?

Most logically straight-forwarding solution:
Turn object into entries array ([[key1, val1], [key2, val2], ...]) using Object.entries
Swap each of the entries ([[val1, key1], [val2, key2], ...]) using map
Turn back into object using Object.fromEntries
function swapKV (obj) {
const entries = Object.entries(obj)
const swappedEntries = entries.map([k, v] => [v, k])
const swappedObj = Object.fromEntries(swappedEntries)
return swappedObj
}
...or more concise:
const swapKV = obj => Object.fromEntries(Object.entries(obj).map([k, v] => [v, k]))
(Of course, another solution would be to just add if (String(x) !== String(key)) delete product[x] to your code. The condition is there to avoid deleting the entry altogether in case key and value are equal when converted to a string.)

This may help!
const swapKV = (obj) => {
const newObj = {};
for (let val in obj) {
newObj[obj[val]] = val;
}
return newObj;
}

You can also try this:
const swap = (obj) => {
return Object.fromEntries(
Object.entries(obj)
.map((key) => key.reverse())`.concat(Object.entries(obj))`
);
};
If the original object is to be also added to the output, concat can be used. The user asks for a function which also keeps the old properties. Otherwise no need.

Related

Reduce with obj key, value not working as expected

I'm trying to dynamically create a sqlite insert string with node. I have:
function update_json_obj_into_table(tablename, obj, search_condition) {
const insertValues = Object.entries(obj).reduce((acc, [k,v]) => acc.push(`${k} = ${v}`), []);
const insertValuesString = Array.prototype.join.call(insertValues, '/');
console.log(insertValuesString);
console.log(`UPDATE ${tablename} SET ${insertValues} WHERE ${search_condition}`);
// db.run(`UPDATE ${tablename} SET ${insertValuesString} WHERE search_condition ;`);
};
When I run it :
const obj = {Code: 'A1'};
update_json_obj_into_table('mytable', obj, 'search_condition')
I get:
UPDATE mytable SET 1 WHERE search_condition.
Obviously the insertValues is not working correctly. I was expecting:
Code = 1
but its coming out as '1' . What am I doing wrong?
.push returns the new length of the array, not the mutated array. While you could put the .push on its own line and return the array on the next line:
function update_json_obj_into_table(tablename, obj, search_condition) {
const insertValues = Object.entries(obj).reduce((acc, [k, v]) => {
acc.push(`${k} = ${v}`);
return acc;
}, []);
const insertValuesString = Array.prototype.join.call(insertValues, '/');
console.log(insertValuesString);
console.log(`UPDATE ${tablename} SET ${insertValues} WHERE ${search_condition}`);
// db.run(`UPDATE ${tablename} SET ${insertValuesString} WHERE search_condition ;`);
};
const obj = {
Code: 'A1'
};
update_json_obj_into_table('mytable', obj, 'search_condition')
It would make a lot more sense to use .map, since the input array and ouput insertValues array are one-to-one:
function update_json_obj_into_table(tablename, obj, search_condition) {
const insertValues = Object.entries(obj)
.map(([k, v]) => `${k} = ${v}`);
const insertValuesString = Array.prototype.join.call(insertValues, '/');
console.log(insertValuesString);
console.log(`UPDATE ${tablename} SET ${insertValues} WHERE ${search_condition}`);
// db.run(`UPDATE ${tablename} SET ${insertValuesString} WHERE search_condition ;`);
};
const obj = {
Code: 'A1'
};
update_json_obj_into_table('mytable', obj, 'search_condition')
That said, dynamically constructed queries like these are rarely a good idea - unless the input is trustworthy, it can compromise your database. Even if the input is trustworthy, it can be inelegant due to delimiter escaping issues, reserved works, and so on. Better to look up how to use prepared statements instead, so that the database driver handles the interpolation of external values.

Compare key values within object for duplicate updated

This is a follow up post from Compare key values within object for duplicate for a follow up answer.
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-0-id:"123",
attendent-0-name:"John Smith",
attendent-maxGuest:1,
attendent-party-name:"",
}
Thanks to help on here (Rick) I was able to get 90% of the way there.
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) {
map[value] = [map[value], key];
} else {
map[value] = key
}
}
}
return map;
}
I am getting a return of:
array:[
0:(2) ["attendent-0-name", "attendent-1-name"]
1:"attendent-2-name"
]
but I am looking for:
array:[
0:(2) ["attendent-0-name", "attendent-1-name", "attendent-2-name"]
]
The issue I am having is that while this works if there are two matching keys it will not work (Correctly) if there are three or more.
If you want to have an array of all matches for each key in you map, you need to start by setting an array when you find a key the first time. On subsequent matches, just push into that array:
const myObj = {'attendent-0-id': "1234",'attendent-0-name': "Bob Smith",'attendent-1-id': "123",'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) {
map[value].push(key); // push a new value
} else {
map[value] = [key] // set it to an array
}
}
}
/* if you only want lengths > 1
otherwise just return map */
let filtered = Object.entries(map)
.reduce((a, [key, value]) => value.length > 1 ? Object.assign(a, {[key]: value}) : a, {})
return filtered;
}
console.log(errorOnDuplicateIds(myObj));
If you are only interested in values with more than one hit, you can reduce() down to a map with only values of length greater than one, which is what the last bit in the snippet does.

JSON.stringify(localStorage) - filtering by key

I use a small code snipped to save the localStorage of my application as a string:
var saveStr = JSON.stringify(localStorage);
It works great at a first glance, but it basically dump the entire localStorage object, which I don't want. I'd like to stringify the localStorage, but only the keys that contains a certain string.
For instance:
var saveStr = JSON.stringify(filteredLS("example"));
filteredLS should return the localStorage data, but only the keys that contains the string that was passed as an argument.
Someone knows an easy snipped to achieve this?
Thanks!
Try this
function filteredLS(term) {
var filteredObj = {};
Object.keys(localStorage)
.filter(function (key) {
return key.indexOf(term) >= 0;
})
.map(function (key) {
filteredObj[key] = localStorage.getItem(key);
});
return JSON.stringify(filteredObj);
}
You should use the methods localStorage.getItem and localStorage.setItem. With those, you can write your own get & set functions to easily use JSON objects:
function get(item) {
return JSON.parse(localStorage.getItem(item))
}
function set(item, value) {
return localStorage.setItem(item, JSON.stringify(value))
}
// use like this:
set('foo', { bar: 1 })
var result = get('foo')
// result: { bar: 1 }
Depending on your target browser you may want to transpile this, but for brevity I'm going with a (mostly) es6 style - this should run in modern browsers
Filtering an object by keys:
const filterByKeys = obj => keys => Object.entries(obj)
// keep only the keys we care about
.filter( ([key, val]) => keys.includes(key) )
// make a new object with just the filtered keys
.reduce( (accum, [key, val]) => Object.assign(accum, {[key]:val} ), {} )
Usage:
// create a function for getting select keys
const localStore = filterByKeys(localStorage)
// call that function with a list of keys you want
const myValues = localStore(['foo', 'bar'])
// and JSON for completeness
const localStoreJson = keys => JSON.stringify(localStore(keys))
Alternate option if you're transpiling or reading this in the future - using spread operator and compacting filter+reduce into one step - for your purposes this is likely unnecessary:
const filterByKeys = obj => keys => Object.entries(obj)
// filter and reduce in one step
.reduce( (accum, [key, val]) => keys.includes(key) ? {...accum, [key]:val } : accum, {} )

How can I get a key in a JavaScript 'Map' by its value?

I have a JavaScript 'Map' like this one
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
I want some method to return a key by its value.
let jhonKey = people.getKey('jhon'); // jhonKey should be '1'
You can use a for..of loop to loop directly over the map.entries and get the keys.
function getByValue(map, searchValue) {
for (let [key, value] of map.entries()) {
if (value === searchValue)
return key;
}
}
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
console.log(getByValue(people, 'jhon'))
console.log(getByValue(people, 'abdo'))
You could convert it to an array of entries (using [...people.entries()]) and search for it within that array.
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
let jhonKeys = [...people.entries()]
.filter(({ 1: v }) => v === 'jhon')
.map(([k]) => k);
console.log(jhonKeys); // if empty, no key found otherwise all found keys.
Though late and other great answers already exist, still you can give the below "..." and "Array.find" a try:
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
function getKey(value) {
return [...people].find(([key, val]) => val == value)[0]
}
console.log('Jasmein - ', getKey('jasmein'))
console.log('Jhon - ', getKey('jhon'))
JavaScript Map and Object
Given a JavaScript Map, I like Nitish's answer:
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
function getKey(val) {
return [...map].find(([key, value]) => val === value)[0];
}
console.log(getKey('one')); // 1
console.log(getKey('two')); // 2
console.log(getKey('three')); // 3
For a JavaScript object, you could do something like so:
const map = {
1: 'one',
2: 'two',
3: 'three',
};
function getKey(val) {
return Object.keys(map).find(key => map[key] === val);
}
console.log(getKey('one')); // 1
console.log(getKey('two')); // 2
console.log(getKey('three')); // 3
There isn't any direct method for picking out information in this direction, so if all you have is the map you need to loop through the set as suggested by others.
If the map/array/other is large enough that such a loop would be a performance issue and the requirement for a reverse lookup is common within the project, you could implement your own structure using a pair of maps/arrays/other with one as per the current object and the other with the key and value reversed.
That way, the reverse lookup is as efficient as the normal one. Of course, you have more work to do as you need to implement each method that you need as a pass-through to one or both of the underlying objects so if the map is small and/or the reverse lookup is not needed often the scan-via-loop option is likely to be preferable due to being simpler to maintain and possible simpler for the JiT compiler to optimise.
In any case, one thing to be wary of is the possibility that multiple keys could have the same value. If this is possible then when looping through your map you need to decide if you are fine to return one of the possible keys arbitrarily (probably the first one) or if you want to return an array of keys, and if implementing a reverse index for data that could have duplicate values the same issue also needs to be accounted for.
One could invert the Map so that the keys are the values and the values are the keys and then lookup the original value as a key. Here's an example:
let myMap = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let invertedMap = new Map([...myMap.entries()].map(
([key, value]) => ([value, key]))
);
console.log(invertedMap.get('one'))
// => 1
Here is a properly typed Typescript solution that doesn't unnecessarily create an array.
function find_map_value<K, V>(m: Map<K, V>, predicate: (v: V) => boolean): [K, V] | undefined {
for (const [k, v] of m) {
if (predicate(v)) {
return [k, v];
}
}
return undefined;
}
If you want all values you can use a generator:
function* find_all_map_values<K, V>(m: Map<K, V>, predicate: (v: V) => boolean): Generator<[K, V]> {
for (const [k, v] of m) {
if (predicate(v)) {
yield [k, v];
}
}
}
Why not simply make use of map's built in iterator prototype/instance reference looking for the target value? Injection into the prototype chain/polyfill inspired solution of sorts makes it universal to ones code:
Map.prototype.getKey = function(targetValue){
let iterator = this[Symbol.iterator]()
for (const [key, value] of iterator) {
if(value === targetValue)
return key;
}
}
const people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
const jhonKey = people.getKey('jhon');
console.log(`The key for 'jhon' is: ${jhonKey}`);
For anyone curious why I added yet another answer. Most of these answers (exception, I like Rajesh's answer, but I added to the prototype chain) are doing a lot of data duplication in the name of finding a value by using the spread operator or even straight up crafting Arrays. Object.keys() mind you is also terribly nonperformant.
Note, I use for..of which iterates on iterables. One could do short hand simply with for(const [key, value] of this){...} if desired.
Tailing off what Maciej Krawczyk suggested here is a general circular map implementation for that.
class ReferenceMap {
#left = new Map();
#right = new Map();
constructor(iterable = []) {
this.#left = new Map(iterable);
this.#right = new Map(ReferenceMap.swapKeyValues(iterable));
}
has(key) {
return this.#left.has(key) || this.#right.has(key);
}
get(key) {
return this.#left.has(key) ? this.#left.get(key) : this.#right.get(key);
}
set(key, value) {
this.#left.set(key, value);
this.#right.set(value, key);
}
delete(key) {
if (this.#left.has(key)) {
let ref = this.#left.get(key);
this.#left.delete(key);
this.#right.delete(ref);
} else if (this.#right.has(key)) {
let ref = this.#right.get(key);
this.#right.delete(key);
this.#left.delete(ref);
}
}
entries() {
return this.#left.entries();
}
keys() {
return this.#left.keys();
}
values() {
return this.#left.values();
}
[Symbol.iterator]() {
return this.entries();
}
get size() {
return this.#left.size;
}
static * swapKeyValues(entries) {
for (let [key, value] of entries) yield [value, key];
}
}
My TypeScript version:
const getByValue = <A, B>(m: Map<A,B>, searchValue: B):[A, B] | undefined => {
const l:IterableIterator<[A, B]> = m.entries();
const a:[A, B][] = Array.from(l);
return a.find(([_k,v]) => v === searchValue);
}
Cache
The question is a bit wrong because one value can be assigned to many keys. Therefore, the result for a given value should be an array of keys (not a single key). If you want to oftet make such search you can use following cache generator for reverse map
let genRevMapCache = map => [...map.entries()].reduce((a,[k,v]) => {
if(!a.get(v)) a.set(v,[]);
a.get(v).push(k);
return a;
}, new Map() );
let genRevMapCache = map => [...map.entries()].reduce((a,[k,v]) => {
if(!a.get(v)) a.set(v,[]);
a.get(v).push(k);
return a;
}, new Map() );
// TEST
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
people.set('4', 'jhon');
let cache = genRevMapCache(people);
console.log('jasmein', cache.get('jasmein'));
console.log('jhon', cache.get('jhon'));
JS:
// Returns keys for all instances
function findAll(obj) {
return Array.from(items.keys()).map(k => items.get(k) === obj ? k : undefined).filter(k => k);
}
// Returns keys for the first instances
function findFirst(obj) {
return Array.from(items.keys()).find(k => items.get(k) === obj);
}
Typescript:
protected items = new Map<TKey, TObject>();
public findAll(obj: TObject): Array<TKey> {
return Array.from(this.items.keys()).map(k => this.items.get(k) === obj ? k : undefined).filter(k => !!k);
}
public findFirst(obj: TObject): TKey | undefined {
return Array.from(this.items.keys()).find(k => this.items.get(k) === obj);
}
Explanation:
// Gets the keys as an array
Array.from(this.items.keys())
// Map the keys whose object matches the instance of `obj` to the key itself, undefined otherwise
.map(k => this.items.get(k) === obj ? k : undefined)
// Filter out array elements that are undefined
// (!! is for strict type-checking/readability practices, you can simply use k => k)
.filter(k => !!k)
// Finds the first occurrence of the key for the given object, undefined if not found
.find(k => this.items.get(k) === obj)

Loop in const object

I am trying to do the following:
const obj {
for(i=0;i<data.length;i++){
Key[i]: data[i].description
}
}
(I know I probably also needs to add a comma at the end of each line except on the last one, but I already get errors in the earlier stage)
This doesn't seem to be allowed in JS. Are there any alternatives? Thank you!
You could use Object.assign in combination with spread syntax ... and map the single objects with Array#map and use computed property names for the object.
const obj = Object.assign(...data.map((o, i) => ({ ['Key' + i]: o.description })));
It looks like you're trying to create an object from a keys array and a data array.
A clean approach would be to use array reduction:
const obj = data.reduce((obj, d, i) => {
obj[Key[i]] = d.description;
return obj;
}, {});
which, assuming your environment allows it, can be simplified further (due note that this will be cleaner code but less efficient because the object spread copies the entire object every time):
const obj = data.reduce((obj, d, i) => ({
...obj,
[Key[i]]: d.description
}), {});
but you could also use a simple for loop:
const obj = {};
for (let i = 0; i < data.length; i++) {
obj[Key[i]] = data[i].description;
}
Note: The code above will break if Key.length !== data.length.

Categories

Resources