Typescript / JavaScript compare undefined values - javascript

by http method Im downloading dto with some variables and some of them may be undefined.
example Http response (it may contains more variables like SO2, C6H6 etc...):
{
'city_name': 'warszawa',
'pollution_types': {
'CO': 506.0,
'O3': 52.66
}
}
Dtos:
export interface PollutionTypes {
CO: number;
NO2: number;
SO2: number;
PM10: number;
PM25: number;
C6H6: number;
O3: number;
}
export interface AirQualityData {
city_name: string;
pollution_types: PollutionTypes;
}
At this moment Im trying to compare this in this way but it doesnt work.
tmp = pollutionTypes.map(value => value.CO);
if (tmp != undefined || tmp != null) {
//do something
}
When Im trying to display this object on console Im getting something like that:
[undefined]
0: undefined
length: 1
But this is not equal to undefined :/
Do u know how to solve this? Thanks for your answers.

You are getting the correct thing in your console, it shows you that you get an array. The contents just happens to be undefined.
const arrayWithUndefined = [undefined];
console.log(arrayWithUndefined);
You could filter your array and remove undefined values. And then see if your array has values in it.
const types = {
"city_name": "warszawa",
"pollution_types": {
"O3": 52.66
}
}; // NO CO present in this data to simulate undefined.
const pollutionTypes = [types.pollution_types];
tmp = pollutionTypes.map(value => value.CO);
console.log('Before filter:', tmp);
tmp = tmp.filter(function( element ) {
return element !== undefined;
});
console.log('After filter:', tmp);
if (tmp.length) { // Check if tmp has values in it.
console.log('tmp has elements in it');
//do something
} else {
console.log('tmp is empty');
}
This will return tmp is empty because no CO values where in the array.

let us go through the problems proposed in your question, though it might be more useful to add more information such as sample data of your pollutionTypes variable.
Let us begin:
// So currently you have the following mapping code...
tmp = pollutionTypes.map(value => value.CO);
// ^^^^^^
// The problem is that you are mapping an array that does not have the field
// you are referencing in the mapping function. So it will map for sure,
// its just that it will return an array of undefined values of the same
// size as the array
if (tmp != undefined || tmp != null) {
//...
}
// and apparently when you log tmp, you get [undefined]
So here is a proposed explanation and solution:
/*
* The map function is called on arrays, it allows you to convert an array
* of size N to a new array of the same size N by parsing it a mapping function
* that allows you to manipulate and return derived elements of each element in the
* old array.
*/
// [TIP]: so first we should use const when defining tmp
const tmp = pollutionTypes.map(value => value.CO);
// ^^^^^^
// So generally try making sure that your data has the CO field
// before mapping...because you're just gonna get an array of undefined
// fields
// [TIP]: you do not need to specify the undefined logic
if (tmp) {
//...
}
Can you add more information on what pollutionTypes is so a better solution
can come up. Anyway, hope this helps.

Related

why cant I access the object values within state when they are clearly shown? [duplicate]

In my code, I deal with an array that has some entries with many objects nested inside one another, where as some do not. It looks something like the following:
// where this array is hundreds of entries long, with a mix
// of the two examples given
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
This is giving me problems because I need to iterate through the array at times, and the inconsistency is throwing me errors like so:
for (i=0; i<test.length; i++) {
// ok on i==0, but 'cannot read property of undefined' on i==1
console.log(a.b.c);
}
I am aware that I can say if(a.b){ console.log(a.b.c)}, but this is extraordinarily tedious in cases where there are up to 5 or 6 objects nested within one another. Is there any other (easier) way that I can have it ONLY do the console.log if it exists, but without throwing an error?
Update:
If you use JavaScript according to ECMAScript 2020 or later, see optional chaining.
TypeScript has added support for optional chaining in version 3.7.
// use it like this
obj?.a?.lot?.of?.properties
Solution for JavaScript before ECMASCript 2020 or TypeScript older than version 3.7:
A quick workaround is using a try/catch helper function with ES6 arrow function:
function getSafe(fn, defaultVal) {
try {
return fn();
} catch (e) {
return defaultVal;
}
}
// use it like this
console.log(getSafe(() => obj.a.lot.of.properties));
// or add an optional default value
console.log(getSafe(() => obj.a.lot.of.properties, 'nothing'));
What you are doing raises an exception (and rightfully so).
You can always do:
try{
window.a.b.c
}catch(e){
console.log("YO",e)
}
But I wouldn't, instead think of your use case.
Why are you accessing data, 6 levels nested that you are unfamiliar of? What use case justifies this?
Usually, you'd like to actually validate what sort of object you're dealing with.
Also, on a side note you should not use statements like if(a.b) because it will return false if a.b is 0 or even if it is "0". Instead check if a.b !== undefined
If I am understanding your question correctly, you want the safest way to determine if an object contains a property.
The easiest way is to use the in operator.
window.a = "aString";
//window should have 'a' property
//lets test if it exists
if ("a" in window){
//true
}
if ("b" in window){
//false
}
Of course you can nest this as deep as you want
if ("a" in window.b.c) { }
Not sure if this helps.
Try this. If a.b is undefined, it will leave the if statement without any exception.
if (a.b && a.b.c) {
console.log(a.b.c);
}
If you are using lodash, you could use their has function. It is similar to the native "in", but allows paths.
var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
//Safely access your walrus here
}
If you use Babel, you can already use the optional chaining syntax with #babel/plugin-proposal-optional-chaining Babel plugin. This would allow you to replace this:
console.log(a && a.b && a.b.c);
with this:
console.log(a?.b?.c);
If you have lodash you can use its .get method
_.get(a, 'b.c.d.e')
or give it a default value
_.get(a, 'b.c.d.e', default)
I use undefsafe religiously. It tests each level down into your object until it either gets the value you asked for, or it returns "undefined". But never errors.
This is a common issue when working with deep or complex json object, so I try to avoid try/catch or embedding multiple checks which would make the code unreadable, I usually use this little piece of code in all my procect to do the job.
/* ex: getProperty(myObj,'aze.xyz',0) // return myObj.aze.xyz safely
* accepts array for property names:
* getProperty(myObj,['aze','xyz'],{value: null})
*/
function getProperty(obj, props, defaultValue) {
var res, isvoid = function(x){return typeof x === "undefined" || x === null;}
if(!isvoid(obj)){
if(isvoid(props)) props = [];
if(typeof props === "string") props = props.trim().split(".");
if(props.constructor === Array){
res = props.length>1 ? getProperty(obj[props.shift()],props,defaultValue) : obj[props[0]];
}
}
return typeof res === "undefined" ? defaultValue: res;
}
I like Cao Shouguang's answer, but I am not fond of passing a function as parameter into the getSafe function each time I do the call. I have modified the getSafe function to accept simple parameters and pure ES5.
/**
* Safely get object properties.
* #param {*} prop The property of the object to retrieve
* #param {*} defaultVal The value returned if the property value does not exist
* #returns If property of object exists it is returned,
* else the default value is returned.
* #example
* var myObj = {a : {b : 'c'} };
* var value;
*
* value = getSafe(myObj.a.b,'No Value'); //returns c
* value = getSafe(myObj.a.x,'No Value'); //returns 'No Value'
*
* if (getSafe(myObj.a.x, false)){
* console.log('Found')
* } else {
* console.log('Not Found')
* }; //logs 'Not Found'
*
* if(value = getSafe(myObj.a.b, false)){
* console.log('New Value is', value); //logs 'New Value is c'
* }
*/
function getSafe(prop, defaultVal) {
return function(fn, defaultVal) {
try {
if (fn() === undefined) {
return defaultVal;
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}(function() {return prop}, defaultVal);
}
Lodash has a get method which allows for a default as an optional third parameter, as show below:
const myObject = {
has: 'some',
missing: {
vars: true
}
}
const path = 'missing.const.value';
const myValue = _.get(myObject, path, 'default');
console.log(myValue) // prints out default, which is specified above
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Imagine that we want to apply a series of functions to x if and only if x is non-null:
if (x !== null) x = a(x);
if (x !== null) x = b(x);
if (x !== null) x = c(x);
Now let's say that we need to do the same to y:
if (y !== null) y = a(y);
if (y !== null) y = b(y);
if (y !== null) y = c(y);
And the same to z:
if (z !== null) z = a(z);
if (z !== null) z = b(z);
if (z !== null) z = c(z);
As you can see without a proper abstraction, we'll end up duplicating code over and over again. Such an abstraction already exists: the Maybe monad.
The Maybe monad holds both a value and a computational context:
The monad keeps the value safe and applies functions to it.
The computational context is a null check before applying a function.
A naive implementation would look like this:
⚠️ This implementation is for illustration purpose only! This is not how it should be done and is wrong at many levels. However this should give you a better idea of what I am talking about.
As you can see nothing can break:
We apply a series of functions to our value
If at any point, the value becomes null (or undefined) we just don't apply any function anymore.
const abc = obj =>
Maybe
.of(obj)
.map(o => o.a)
.map(o => o.b)
.map(o => o.c)
.value;
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script>
function Maybe(x) {
this.value = x; //-> container for our value
}
Maybe.of = x => new Maybe(x);
Maybe.prototype.map = function (fn) {
if (this.value == null) { //-> computational context
return this;
}
return Maybe.of(fn(this.value));
};
</script>
Appendix 1
I cannot explain what monads are as this is not the purpose of this post and there are people out there better at this than I am. However as Eric Elliot said in hist blog post JavaScript Monads Made Simple:
Regardless of your skill level or understanding of category theory, using monads makes your code easier to work with. Failing to take advantage of monads may make your code harder to work with (e.g., callback hell, nested conditional branches, more verbosity).
Appendix 2
Here's how I'd solve your issue using the Maybe monad from monetjs
const prop = key => obj => Maybe.fromNull(obj[key]);
const abc = obj =>
Maybe
.fromNull(obj)
.flatMap(prop('a'))
.flatMap(prop('b'))
.flatMap(prop('c'))
.orSome('🌯')
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script src="https://www.unpkg.com/monet#0.9.0/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>
In str's answer, value 'undefined' will be returned instead of the set default value if the property is undefined. This sometimes can cause bugs. The following will make sure the defaultVal will always be returned when either the property or the object is undefined.
const temp = {};
console.log(getSafe(()=>temp.prop, '0'));
function getSafe(fn, defaultVal) {
try {
if (fn() === undefined || fn() === null) {
return defaultVal
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}
You can use optional chaining from the ECMAScript standart.
Like this:
a?.b?.c?.d?.func?.()
I answered this before and happened to be doing a similar check today. A simplification to check if a nested dotted property exists. You could modify this to return the value, or some default to accomplish your goal.
function containsProperty(instance, propertyName) {
// make an array of properties to walk through because propertyName can be nested
// ex "test.test2.test.test"
let walkArr = propertyName.indexOf('.') > 0 ? propertyName.split('.') : [propertyName];
// walk the tree - if any property does not exist then return false
for (let treeDepth = 0, maxDepth = walkArr.length; treeDepth < maxDepth; treeDepth++) {
// property does not exist
if (!Object.prototype.hasOwnProperty.call(instance, walkArr[treeDepth])) {
return false;
}
// does it exist - reassign the leaf
instance = instance[walkArr[treeDepth]];
}
// default
return true;
}
In your question you could do something like:
let test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
containsProperty(test[0], 'a.b.c');
I usually use like this:
var x = object.any ? object.any.a : 'def';
You can avoid getting an error by giving a default value before getting the property
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
for (i=0; i<test.length; i++) {
const obj = test[i]
// No error, just undefined, which is ok
console.log(((obj.a || {}).b || {}).c);
}
This works great with arrays too:
const entries = [{id: 1, name: 'Scarllet'}]
// Giving a default name when is empty
const name = (entries.find(v => v.id === 100) || []).name || 'no-name'
console.log(name)
Unrelated to the question's actual question, but might be useful for people coming to this question looking for answers.
Check your function parameters.
If you have a function like const x({ a }) => { }, and you call it without arguments x(); append = {} to the parameter: const x({ a } = {}) => { }.
What I had
I had a function like this:
const x = ({ a }) => console.log(a);
// This one works as expected
x({ a: 1 });
// This one errors out
x();
Which results in "Uncaught TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined."
What I switched it to (now works).
const x = ({ a } = {}) => console.log(a);
// This one works as expected
x({ a: 1 });
// This now works too!
x();

Trying to create a hash table in JS... Can't figure out how to write a "get" function with the given hash function without being given an index

So I have been able to create a set function that seems to work correctly. The problem comes when I try to create a 'get' function that searches the hashtable passed on a 'key' passed in as an argument.
The hashing function takes a 'string' and 'size' argument, not a 'key' like all the examples I looked at trying to figure it out. Here is the hashing function I was given...
function hashCode(string, size){
let hash = 0;
if (string.length == 0) return hash;
for (let i = 0; i < string.length; i++) {
const letter = string.charCodeAt(i);
hash = ((hash << 5) - hash) + letter;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash) % size ;
}
Here is my 'set' 'HashTable' class function andthe 'set' function I wrote...
function HashTable() {
this.SIZE = 16;
this.storage = new Array(this.SIZE);
}
// stores a value in the storage array
HashTable.prototype.set = function(key, value) {
let index = hashCode(value, 16);
if (!this.storage[index]) {
this.storage[index] = [];
}
this.storage[index][key] = value
};
I have tried a few different methods to make the 'get' function work. I have tried iterating through the array and using the .hasOwnProperty method and currently tried to just use dot notation in a loop to find the property (what is shown below). I can't seem to get it to work with the methods I listed and can't think of any other ways to find the key/value pair in the array without being able to get an index from the hashing function.
Here is the 'get' method I am working on...
HashTable.prototype.get = function(key) {
this.storage.forEach((kvpair) => {
if (kvpair.key) {
return kvpair.key
}
})
};
when I create a new instance of the class like this...
let table = new HashTable;
and run the 'set' function...
table.set('key','value');
and console.log 'table' I get this...
HashTable {SIZE: 16, storage: [ , [ key: 'value' ], , , , , , , , , , , , , , ] }
when I attempt to run my 'get' method...
table.get('key')
undefined is logged to the console...
I am just unsure of how to make this 'get' function work without the index... I am obviously not retrieving the value correctly with my loop and dot notation...
Any tips, tricks, ideas, hints, or help will be greatly appreciated!
The problem is that your get method doesn't have a return statement. True, the callback that is passed to forEach has a return statement, but that defines the return value of the callback, not of the get method.
Moreover, returning a value inside a forEach callback is useless: that returned value is going nowhere. forEach does not do anything with it.
Instead I would suggest using find:
HashTable.prototype.get = function(key) {
return this.storage.find(kvpair => kvpair.key)?.key;
};
This will also iterate over the key/value pairs, but find is designed to stop the iteration as soon as the callback returns a truthy value. Since you want key to be truthy, it is enough to return kvpair.key inside that callback. Then find will return the kvpair for which this key is truthy. It then remains to grab again the key property.
The ?. operator will make sure that if the key is not found, and find will return undefined, that no error will occur, but that undefined will be returned instead of accessing a property on undefined.
I slightly changed your get function:
HashTable.prototype.get = function(key) {
var value = null
this.storage.forEach((kvpair) => {
if (kvpair.key) {
value = kvpair.key;
}
})
return value;
};
I have no idea why this works and your code does not...
If anyone can explain why, thank you.

Typescript, JSON.parse error: "Type 'null' is not assignable to type 'string'."

The error was happening here:
let moonPortfolio;
...
moonPortfolio = JSON.parse(localStorage.getItem('moonPortfolio'));
I found this answer which makes sense, however I'm still getting that error after this refactor:
As the error says, localStorage.getItem() can return either a string or null. JSON.parse() requires a string, so you should test the result of localStorage.getItem() before you try to use it.
if (portfolio.length === 0) {
const storedPortfolio = localStorage.getItem('moonPortfolio');
if (typeof storedPortfolio === 'string') {
moonPortfolio = JSON.parse(localStorage.getItem('moonPortfolio'));
}
else {
moonPortfolio = [];
}
if (moonPortfolio) {
const savedPortfolio = Object.values(moonPortfolio);
this.props.fetchAllAssets();
// this.props.addCoins(savedPortfolio);
}
}
I first set the results of localStorage moonPortfolio to a var, then check if the var is typeof string. Yet still getting the typescript error?
Any thoughts or direction here?
Simple fix:
JSON.parse(localStorage.getItem('moonPortfolio') || '{}');
Seems like TS does know about the inner workings of localStorage/sessionStorage actually. It returns null if you try to fetch a key that isn't set. null when treated as boolean is falsy so by adding OR the empty stringified json object will be used instead meaning that JSON.parse(x) will always be given a string meaning it's then type safe.
The compiler doesn't know too much about the inner workings of localStorage.getItem and doesn't make the assumption that the return value will be the same from one call of getItem to the next. So it just tells you that it can't be certain that on the second call to getItem the result isn't null.
Try simply passing in the variable you've already created instead of reading from localStorage again:
if (typeof storedPortfolio === 'string') {
moonPortfolio = JSON.parse(storedPortfolio);
}
TypeScript doesn't know that multiple invocations of localStorage.getItem with the same string literal will always return the same value (in fact, this isn't even true).
The second call to localStorage.getItem('moonPortfolio') may well return null - you should call JSON.parse(storedPortfolio) instead of calling getItem again.
The main thing to know is that localStorage.getItem() returns string | null. Knowing that we can re-write the code with a simpler pattern:
const portfolio = []; //Just to make the code samples valid
if (portfolio.length === 0) {
let moonPortfolio = []; //Default value
const storedText = localStorage.getItem('moonPortfolio');
if (storedText !== null) { //We know it's a string then!
moonPortfolio = JSON.parse(storedText);
}
//The if statement here is not needed as JSON.parse can only return
//object | array | null or throw. null is the only falsy value and that
//can only happen if storedText is null but we've already disallowed
//that.
//if (moonPortfolio) {
const savedPortfolio = Object.values(moonPortfolio);
//Do whatever else is needed...
//}
}
also in react:
JSON.parse(localStorage.getItem("data") ?? '{}')
or
JSON.parse(localStorage.getItem("data") || '{}')

Access sub-property with generic/dynamic property list [duplicate]

I have a bunch of object attributes coming in as dot-delimited strings like "availability_meta.supplier.price", and I need to assign a corresponding value to record['availability_meta']['supplier']['price'] and so on.
Not everything is 3 levels deep: many are only 1 level deep and many are deeper than 3 levels.
Is there a good way to assign this programmatically in Javascript? For example, I need:
["foo.bar.baz", 1] // --> record.foo.bar.baz = 1
["qux.qaz", "abc"] // --> record.qux.qaz = "abc"
["foshizzle", 200] // --> record.foshizzle = 200
I imagine I could hack something together, but I don't have any good algorithm in mind so would appreciate suggestions. I'm using lodash if that's helpful, and open to other libraries that may make quick work of this.
EDIT this is on the backend and run infrequently, so not necessary to optimize for size, speed, etc. In fact code readability would be a plus here for future devs.
EDIT 2 This is NOT the same as the referenced duplicate. Namely, I need to be able to do this assignment multiple times for the same object, and the "duplicate" answer will simply overwrite sub-keys each time. Please reopen!
You mentioned lodash in your question, so I thought I should add their easy object set() and get() functions. Just do something like:
_.set(record, 'availability_meta.supplier.price', 99);
You can read more about it here: https://lodash.com/docs#set
These functions let you do more complex things too, like specify array indexes, etc :)
Something to get you started:
function assignProperty(obj, path, value) {
var props = path.split(".")
, i = 0
, prop;
for(; i < props.length - 1; i++) {
prop = props[i];
obj = obj[prop];
}
obj[props[i]] = value;
}
Assuming:
var arr = ["foo.bar.baz", 1];
You'd call it using:
assignProperty(record, arr[0], arr[1]);
Example: http://jsfiddle.net/x49g5w8L/
What about this?
function convertDotPathToNestedObject(path, value) {
const [last, ...paths] = path.split('.').reverse();
return paths.reduce((acc, el) => ({ [el]: acc }), { [last]: value });
}
convertDotPathToNestedObject('foo.bar.x', 'FooBar')
// { foo: { bar: { x: 'FooBar' } } }
Just do
record['foo.bar.baz'] = 99;
But how would this work? It's strictly for the adventurous with a V8 environment (Chrome or Node harmony), using Object.observe. We observe the the object and capture the addition of new properties. When the "property" foo.bar.baz is added (via an assignment), we detect that this is a dotted property, and transform it into an assignment to record['foo']['bar.baz'] (creating record['foo'] if it does not exist), which in turn is transformed into an assignment to record['foo']['bar']['baz']. It goes like this:
function enable_dot_assignments(changes) {
// Iterate over changes
changes.forEach(function(change) {
// Deconstruct change record.
var object = change.object;
var type = change.type;
var name = change.name;
// Handle only 'add' type changes
if (type !== 'add') return;
// Break the property into segments, and get first one.
var segments = name.split('.');
var first_segment = segments.shift();
// Skip non-dotted property.
if (!segments.length) return;
// If the property doesn't exist, create it as object.
if (!(first_segment in object)) object[first_segment] = {};
var subobject = object[first_segment];
// Ensure subobject also enables dot assignments.
Object.observe(subobject, enable_dot_assignments);
// Set value on subobject using remainder of dot path.
subobject[segments.join('.')] = object[name];
// Make subobject assignments synchronous.
Object.deliverChangeRecords(enable_dot_assignments);
// We don't need the 'a.b' property on the object.
delete object[name];
});
}
Now you can just do
Object.observe(record, enable_dot_assignments);
record['foo.bar.baz'] = 99;
Beware, however, that such assignments will be asynchronous, which may or may not work for you. To solve this, call Object.deliverChangeRecords immediately after the assignment. Or, although not as syntactically pleasing, you could write a helper function, also setting up the observer:
function dot_assignment(object, path, value) {
Object.observe(object, enable_dot_assignments);
object[path] = value;
Object.deliverChangeRecords(enable_dot_assignments);
}
dot_assignment(record, 'foo.bar.baz', 99);
Something like this example perhaps. It will extend a supplied object or create one if it no object is supplied. It is destructive in nature, if you supply keys that already exist in the object, but you can change that if that is not what you want. Uses ECMA5.
/*global console */
/*members split, pop, reduce, trim, forEach, log, stringify */
(function () {
'use strict';
function isObject(arg) {
return arg && typeof arg === 'object';
}
function convertExtend(arr, obj) {
if (!isObject(obj)) {
obj = {};
}
var str = arr[0],
last = obj,
props,
valProp;
if (typeof str === 'string') {
props = str.split('.');
valProp = props.pop();
props.reduce(function (nest, prop) {
prop = prop.trim();
last = nest[prop];
if (!isObject(last)) {
nest[prop] = last = {};
}
return last;
}, obj);
last[valProp] = arr[1];
}
return obj;
}
var x = ['fum'],
y = [
['foo.bar.baz', 1],
['foo.bar.fum', new Date()],
['qux.qaz', 'abc'],
['foshizzle', 200]
],
z = ['qux.qux', null],
record = convertExtend(x);
y.forEach(function (yi) {
convertExtend(yi, record);
});
convertExtend(z, record);
document.body.textContent = JSON.stringify(record, function (key, value, Undefined) {
/*jslint unparam:true */
/*jshint unused:false */
if (value === Undefined) {
value = String(value);
}
return value;
});
}());
it's an old question, but if anyone still looking for a solution can try this
function restructureObject(object){
let result = {};
for(let key in object){
const splittedKeys = key.split('.');
if(splittedKeys.length === 1){
result[key] = object[key];
}
else if(splittedKeys.length > 2){
result = {...result, ...{[splittedKeys.splice(0,1)]: {}} ,...restructureObject({[splittedKeys.join('.')]: object[key]})}
}else{
result[splittedKeys[0]] = {[splittedKeys[1]]: object[key]}
}
}
return result
}

Get object literal string with extra string embellishments around it

I currently have an object literal in Typescript. Something like:
const MyNamesStrings = {
a: {
b: "hello",
c: "bye"
}
d: {
e: "qwerty"
}
}
But I want to wrap them in some strings every time I access them. The strings are the same, and would look very ugly and repetitive if it's in the literal. This would allow me to have a much easier time maintaining the strings.
I want to create a MyNames class that acts like a proxy with the functionality to do this:
const ab = MyNames.a.b //"[${hello}]" where the extra characters surround the text.
const ac = MyNames.a.c //"[${bye}]"
Is this feasible in Javascript/Typescript? If it's not, I can always just do it in more mundane ways.
If you really need the behavior you describe, you could use an actual Proxy, if your JavaScript engine supports ECMAScript 2015 or later. This might be overkill for your use case; if MyNamesStrings is immutable, a one-time transformation from MyNamesStrings to MyNames would be more efficient. But let's assume you do need a proxy. Here's one way to implement it:
function makeStringWrappingProxy<T extends object>(t: T): T {
return new Proxy(t, {
get<K extends keyof T>(target: T, prop: K) {
const val = target[prop];
if (typeof val === 'string') {
return '[${' + val + '}]';
} else if (typeof val === 'object') {
return makeStringWrappingProxy(val as T[K] & object);
} else {
return val;
}
}
});
}
The idea is return a proxy which that intercepts all property retrievals to the object. If you are getting a string property, return the wrapped string instead. If you are getting an object property, return a proxy for that property instead (this allows you to drill down into properties of properties and still see them wrapped). Otherwise, just return the property.
Let's see it in action:
const MyNames = makeStringWrappingProxy(MyNamesStrings);
const ab = MyNames.a.b //"[${hello}]" as expected
const ac = MyNames.a.c //"[${bye}]" as expected as well.
So it works! Again, this assumes you actually want to do it this way. Proxies aren't particularly performant (each property access kicks off function calls), backward compatible (ES5 doesn't support it), or intuitive (you can modify a property but when you read the property afterward it isn't what you set it to). It's up to you.
Hope that helps; good luck!
basically you could do a custom getter function and instead of accessing to MyNamesStrings.a.b you can do MyNamesStrings.propGetter('a.b')
var MyNamesStrings = {
a: {
b: "hello",
c: "bye"
},
d: {
e: "qwerty"
},
propGetter: function(loc, start = "[${", end = "}]") {
var split = loc.split('.');
var value = this;
for (var i = 0; i < split.length; i++) {
if (value) {
value = value[split[i]];
}else{
//stop looping because the previous value doesn't exist.
break;
}
}
return value ? start + value + end : undefined;
}
}
var result = MyNamesStrings.propGetter('a.b');
var result2 = MyNamesStrings.propGetter('d.e');
var result3 = MyNamesStrings.propGetter('f.g.h.i');
console.log(result);
console.log(result2);
console.log(result3);

Categories

Resources