Suppose I want to create an object with keys as USD and non-USD. Like Below:
let obj = {
USD: {
sourceValue: 100
destinationValue: 10
},
non-USD: {
sourceValue: 10
destinationValue: 100
}
}
Except here, instead of non-USD, I should be able to pass any currency, like SGD or HKD and I should get result of non-USD currency.
so, if I wrote obj[currency].sourceValue and currency=HKD then I should get 10
I don't want to use obj[1].sourceValue as currency value is dynamic.
Also, I don't want to use if(currency!='USD') index=1 and then obj[index].sourceValue
So, my question is, what should I write at place of non-USD while defining object? I checked computation names, but I am not sure, how will I pass long currency array as key name and filter USD out of it?
I don't know of an option in js/ts that would have the key lookup syntax (like obj[currency]) and do the behavior that you want, while keeping the object obj "plain".
But there are options that do what you want with some modifications to the obj and/or with a different call syntax. (The only way that I can think of that would keep the obj completely untouched would require some changes to the currencies then.)
Option 1: add a get function call
const obj1 = {
get: function(currency) {
return this[currency === "USD" ? "USD" : "non-USD"]
},
USD: {
sourceValue: 100,
destinationValue: 10
},
"non-USD": {
sourceValue: 10,
destinationValue: 100
}
}
Or if you cannot change the object's source use Object.assign.
Here you can decide if you want to mutate the original object (Object.assign(target, ...)) or create a new one with Object.assign({}, target, ...)
const addGetter = (target) => Object.assign({}, target, {
get: (currency) => target[currency === "USD" ? "USD" : "non-USD"]
})
const obj1 = addGetter(obj) // <-- pass in the original obj
Usage: obj1.get(currency).sourceValue
Option 2: using a Proxy
Proxy docs, support
Offers the key lookup syntax that you want, but imo, this approach is a bit error-prone, because any access (indexed, by key or property) other than "USD" will return the "non-USD" values. Also the object gets wrapped, which hides object details when logging (console.log(obj)) in some consoles.
const useProxy = (obj) => new Proxy(obj, {
get: (target, currency) => target[currency === "USD" ? "USD" : "non-USD"]
})
const obj2 = useProxy(obj) // <-- pass in the original obj
Usage: obj2[currency].sourceValue
Demo
Check the code comments and the console output
const addGetter = (target) => Object.assign({}, target, {
get: (currency) => target[currency === "USD" ? "USD" : "non-USD"]
})
const useProxy = (obj) => new Proxy(obj, {
get: (target, currency) => target[currency === "USD" ? "USD" : "non-USD"]
})
const obj = {
USD: {
sourceValue: 100,
destinationValue: 10
},
"non-USD": {
sourceValue: 10,
destinationValue: 100
}
}
// Option 1
const obj1 = addGetter(obj)
// Option 2
const obj2 = useProxy(obj)
const c = ["USD", "EUR", "HKD", "non-USD"]
console.log("obj1.get(currency).sourceValue", c.map(currency => currency + " -> " + obj1.get(currency).sourceValue))
console.log("obj2[currency].sourceValue", c.map(currency => currency + " -> " + obj2[currency].sourceValue))
// Option 2 feels a bit error-prone, as any other access will return the fallback value for "non-USD"
console.log("obj2.length", c.map(currency => obj2.length))
console.log("obj2.randomProp", c.map(currency => obj2.randomProp))
You ll actually need to use the if else like condition.
say like this
const key = currency == "USD" ? "USD" : "non-USD";
// and then get the data like this
obj[currency].sourceValue
Related
In a JSON data, I've certain currency data values in the form as follows:
const currencyData = [
{
"₹": "INR"
},
{
"¥": "JPY"
},
{
"€": "EUR"
},
{
"£": "GBP"
}
];
I've created a function which accepts 2 parameters one for 'amount' and other for 'currency code', but, what could be done, when second parameter is "$" or "¥" i.e a currency symbol. My intention is to map the currency symbol with above JSON data and give the relevant output, like for e.g: "$" is passed then it should filter comparing with the above JSON data and pass "USD" output and then it can be passed in the below function
const currencyFormatterNew = (amount, currCode) => {
let currencyCode;
try {
if (currCode == "undefined" || currCode == "") {
currencyCode = "";
} else {
currencyCode = currCode;
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: currencyCode,
minimumFractionDigits: 0
}).format(amount);
}
} catch (err) {
console.log(err);
}
};
The function is called as following: {currencyFormatterNew(10000, "JPY")}
What would be optimal solution to map the 'currency symbol' with above json data and then give required output which could be used in the function created?
Here is the codesandbox link: https://codesandbox.io/s/selection-and-operation-antd-4-17-0-alpha-0-forked-2sqzt
const getCurrencyCode = (code) => {
const k = currencyData.find(a => Object.keys(a) == code);
return Object.entries(k)[0][1];
}
you can try this.
If you want to you can change the last return statement as following
return Object.values(k); // it will return an array
After the JSON has been loaded, store the currencyData array into an object:
const currencyMap = Object.assign({}, ...currencyData)
Then you can just use property accessor [prop] to get the equivalent symbol. This will be better for performance since you don't need to call .find or similar every time your function is called because those functions does a loop.
const currencyData = [
{
"₹": "INR"
},
{
"¥": "JPY"
},
{
"€": "EUR"
},
{
"£": "GBP"
}
];
const currencyMap = Object.assign({}, ...currencyData)
console.log(currencyMap);
let symbol = '₹';
console.log(currencyMap[symbol]);
I'm trying to use Enum's to get a safe event output from an rxjs observable. Here is the full code sample:
// Enum
export enum Key {
ArrowUp = "ArrowUp",
ArrowDown = "ArrowDown",
ArrowLeft = "ArrowLeft",
ArrowRight = "ArrowRight",
W = "w",
A = "a",
S = "s",
D = "d",
One = "1",
Two = "2",
}
// Observable
export const useKeyboardEvent = () => {
const $keyboardEvent: Observable<Key> = fromEvent<KeyboardEvent>(
document,
"keydown"
).pipe(
filter((event: KeyboardEvent) => {
return !!Object.values(Key).find((v) => v === event.key);
}),
map(
(event: KeyboardEvent) =>
Object.keys(Key)[
(Object.values(Key) as string[]).indexOf(event.key)
] as Key
),
distinctUntilChanged()
);
return $keyboardEvent;
};
// Subscription
React.useEffect(() => {
const keyboardEventSub = $keyboardEvent.subscribe(setColor);
return () => {
keyboardEventSub.unsubscribe();
};
}, [$keyboardEvent, setColor]);
// handler
const color = React.useRef<string>("black");
const setColor = React.useCallback((key: Key) => {
if (key === Key.One) {
color.current = "black";
} else if (key === Key.Two) {
color.current = "red";
}
}, []);
When I log out different comparisons, I see this as the result:
key === Key.One false
key in Key true
My question:
How can I compare so that key === Key.One succeeds? And why does that comparison fail, but in succeeds?
Enums are funny things in TypeScript. They're basically maps between a key and a value. So when you pass key you're passing the string "One", but when you get Key.One for comparison, you're getting the string "1". So, "One" === "1" returns false and you're stuck.
When you do key in Key, it looks for the property name One in the Key enum, and finds it, so it returns true. Of course, that just tells you that key is a member of the Key enum, so not very useful.
As an aside, property names are called "keys", as you are likely aware, but I forwent the pun for the sake of clarity. But it was hard to do.
What you can do is make sure you specify the type of the key as Key so that TypeScript does the correct mapping of value to value when asked to. As you discovered, one way of doing that is to change the mapping to:
Object.values(Key).find((v) => v === event.key) as Key
[
{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}
I have an API that response JSON data like this-
{
"unitcode":"apple",
"description":"bus",
"color":"red",
"intent":"Name 1"
}
I want to change like this-
{
"Value1":"apple",
"Value2":"bus",
"value3":"red",
"value4":"sale"
}
Presently, I can code to rename single key but i want some code to replace all key in one shot. my code like this-
request(baseurl)
.then( body => {
var unit = JSON.parse(body);
unit.intent = "sales"
unit.value1 = unit.unitcode
delete unit.unitcode;
console.log(unit)
console.log(unit.Value1)
var unit2 = JSON.stringify(unit)
// let code = unit.description;
conv.ask('Sales is 1 million metric tonnes ' + unit2);
})
please help me out on this and please little elaborate also to learn something new. thanks
Create a Map of original key to new key (transformMap). Convert the object to pairs of [key, value] with Object.entries(), iterate with Array.map() and replace the replacement key from the Map (or the original if not found). Convert back to an object with Object.toEntries():
const transformMap = new Map([
['unitcode', 'Value1'],
['description', 'Value2'],
['color', 'Value3'],
['intent', 'Value4']
]);
const transformKeys = obj =>
Object.fromEntries(
Object.entries(obj)
.map(([k, v]) => [transformMap.get(k) || k, v])
);
const obj = {
"unitcode": "apple",
"description": "bus",
"color": "red",
"intent": "Name 1"
};
const result = transformKeys(obj)
console.log(result)
If you know the object structure and it is constant, you could just use destructing like so.
const data = {
"unitcode":"apple",
"description":"bus",
"color":"red",
"intent":"Name 1"
};
// If the object is fixed and the fields are known.
const mapData = ({ unitcode, description, color, intent }) => ({
Value1: unitcode,
Value2: description,
Value3: color,
Value4: intent
});
console.log(JSON.stringify(mapData(data)));
But if the object has an unknown number of properties:
const data = {
"unitcode":"apple",
"description":"bus",
"color":"red",
"intent":"Name 1"
};
// If the object is fixed and the fields are known.
const mapData = (data) => {
return Object.keys(data).reduce((a,v,i) => {
a[`Value${i+1}`] = data[v];
return a;
}, {});
};
console.log(JSON.stringify(mapData(data)));
You can edit the array to have the values you need
let i=0,j=0,unit1={};
let unit = JSON.parse(body);
let unit3=["val1","val2","val3","val4"]
let unit5=Object.values(unit);
for(let key in unit){
unit1[unit3[i++]]=unit5[j++];
}
var unit2 = JSON.stringify(unit1)
console.log('Sales is 1 million metric tonnes \n' + unit2);
//Sales is 1 million metric tonnes
//{"val1":"apple","val2":"bus","val3":"red","val4":"Name 1"}
Well your target is to modify the keys and retain the value
In that context, you can iterate through your data. To dynamically generate keys as Value1, Value2, etc, we will append Value with iteration index which is going to be unique always.
const modifyInput = (input) => {
const modifiedInput = {}
Object.values(input).forEach((item, index) => {
modifiedInput[`Value${index + 1}`] = item
})
return modifiedInput
}
Use this function, pass your input and get your desired result
I'm writing template software for publishing node.js modules to GitHub and NPM. A small snippet of an example template:
{{module.name}}
{{module.description}}
Purpose
{{module.code.purpose}}
I have an object built up with all these properties, ex.
{
"module" : {
"name" : "test",
"description" : "Test module"
...
The problem is that I need to merge this object's sub-objects so the final output looks something like this (for replacing the placeholders in the templates):
{
"module.name" : "test",
"module.description" : "Test module"
...
So I created my own function to do that:
/**
Takes an object like
{
"type" : "meal",
"pizza" : {
"toppings" : ["pepperoni", "cheese"],
"radius" : 6,
"metadata" : {
"id" : 24553
}
}
}
and changes it to
{
"type" : "meal",
"pizza.toppings" : ["pepperoni", "cheese"],
"pizza.radius" : 6,
"pizza.metadata.id" : 244553
}
*/
const mergeObjectsToKeys = object => {
// Loop through the passed in object's properties
return Object.entries(object).reduce((obj, [key, value]) => {
// Check if it is a sub-object or not
if (typeof value === "object") {
// If it is a sub-object, merge its properties with recursion and then put those keys into the master object
const subObject = mergeObjectsToKeys(value);
Object.entries(subObject).forEach(([key2, value2]) => {
obj[key + "." + key2] = value2;
});
} else {
// Otherwise, just put the key into the object to return
obj[key] = value;
}
}, { });
};
Two questions
Is this the correct way to write the software?
If so, is there a built-in function to merge the sub-objects, like shown above?
One built-in function to handle the requirement is Object.assign(); you can use spread element, Object.entries(), .map() to set property names of object which is property of object.
To handler objects where value is not nested object
let p = Object.keys(o).pop();
let res = Object.assign({}, ...Object.entries(o.module).map(([key, prop]) =>
({[`${p}.${key}`]: prop})));
To handle value which is nested object
let o = {
"type": "meal",
"pizza": {
"toppings": ["pepperoni", "cheese"],
"radius": 6,
"metadata": {
"id": 24553
}
}
}
let res = Object.assign({}, ...Object.entries(o).map(([prop, value]) =>
typeof value === "object" && !Array.isArray(value)
? Object.assign({}, ...Object.entries(value)
.map(([key,val]) => ({[`${prop}.${key}`]: key})))
: {[prop]: value})
);
console.log(res);