I'm having some difficulty trying to write a function that takes two inputs:
- The name of an object
- The name of a property
and prints the value of that property for that object. However, the objects all have different properties, they're not all the same.
The objects look like this:
class object1 {
get property1() {
return 'foo';
}
get property2() {
return 'bar';
}
}
export default new object1();
class object2 {
get different1() {
return 'asdf';
}
get different2() {
return 'ghjk';
}
}
export default new object2();
Here's what I'm tried so far:
import object1 from '..';
import object2 from '..';
getPropertValue(objectName, propertyName) {
let objects = [object1, object2];
let index = objects.indexOf(objectName);
console.log(objects[index][propertyName]);
}
This didn't work, came back as undefined. It seems like index is being calculated correctly, but it doesn't seem like objects[index][propertyName]
is properly accessing the object's value. Though weirdly, when I tried the following, it ALMOST worked:
import object1 from '..';
import object2 from '..';
getPropertValue(objectName, propertyName) {
let objects = [object1, object2];
for (let index in objects) {
console.log(objects[index][propertyName]);
}
}
Here I actually got it to print the correct value, but the problem is that since I'm just iterating over all the objects in a for loop, it tries to print the value for all objects, instead of just the one that matches the objectName. So it prints the correct value for the object that has the property I'm trying to access, but then gets undefined for the other object which does not have that property.
I suppose I could add some property to each object called name
and do something like this:
getPropertValue(objectName, propertyName) {
let objects = [object1, object2];
for (let index in objects) {
if(objects[index].name == objectName) {
console.log(objects[index][propertyName]);
}
}
}
But I'd rather not add unnecessary properties to these objects if I can help it.
Is there a better way to do this?
I'm having some difficulty trying to write a function that takes two inputs:
- The name of an object
- The name of a property
and prints the value of that property for that object. However, the objects all have different properties, they're not all the same.
The objects look like this:
class object1 {
get property1() {
return 'foo';
}
get property2() {
return 'bar';
}
}
export default new object1();
class object2 {
get different1() {
return 'asdf';
}
get different2() {
return 'ghjk';
}
}
export default new object2();
Here's what I'm tried so far:
import object1 from '..';
import object2 from '..';
getPropertValue(objectName, propertyName) {
let objects = [object1, object2];
let index = objects.indexOf(objectName);
console.log(objects[index][propertyName]);
}
This didn't work, came back as undefined. It seems like index is being calculated correctly, but it doesn't seem like objects[index][propertyName]
is properly accessing the object's value. Though weirdly, when I tried the following, it ALMOST worked:
import object1 from '..';
import object2 from '..';
getPropertValue(objectName, propertyName) {
let objects = [object1, object2];
for (let index in objects) {
console.log(objects[index][propertyName]);
}
}
Here I actually got it to print the correct value, but the problem is that since I'm just iterating over all the objects in a for loop, it tries to print the value for all objects, instead of just the one that matches the objectName. So it prints the correct value for the object that has the property I'm trying to access, but then gets undefined for the other object which does not have that property.
I suppose I could add some property to each object called name
and do something like this:
getPropertValue(objectName, propertyName) {
let objects = [object1, object2];
for (let index in objects) {
if(objects[index].name == objectName) {
console.log(objects[index][propertyName]);
}
}
}
But I'd rather not add unnecessary properties to these objects if I can help it.
Is there a better way to do this?
Share Improve this question edited Dec 16, 2022 at 19:20 rmoreltandem asked Dec 16, 2022 at 19:14 rmoreltandemrmoreltandem 751 gold badge2 silver badges6 bronze badges 3-
3
What do you pass in for
objectName
? What does "the name of an object" mean to you exactly? Objects can be referenced by a variable name (which you cannot reference dynamically), but the object itself has no quality of being "named" unless you explicitly make that part of your data model. – Alex Wayne Commented Dec 16, 2022 at 19:24 -
objectName
isobject1
orobject2
. – rmoreltandem Commented Dec 16, 2022 at 19:30 - I guess that means I do have to do the final solution I proposed where I add a "name" property to each object and then use an if statement to filter for the one I need to select? – rmoreltandem Commented Dec 16, 2022 at 19:41
2 Answers
Reset to default 4Objects can be referenced by a variable, but and object has no quality of being named unless your code explicitly provides that as part of your data model.
For instance:
const obj = { abc: 123 }
Here the value { abc: 123 }
is not named obj
. There is a variable with an identifier of obj
that references the value that is { abc: 123 }
.
What this means in practice is that if you only have a reference to { abc: 123 }
, then you cannot know what other variable identifiers also hold a reference to this value.
And you cannot get a variable's value by its identifier as a string.
So this cannot possibly work:
const foo = 'bar'
function getVar(varName: string) {
// impossible function implementation here.
}
getVar('foo') // expected return value: 'bar'
But what you can have is an object with properties. Properties have keys which are specific strings.
const objects = { // note the {}, [] is for arrays
object1,
object2,
}
Which is shorthand for:
const objects = {
"object1": object1,
"object2": object2,
}
You now have object with properties that correspond to the names you want to use for your objects.
Lastly, let's type your function properly:
// store this outside the function
const objects = {
object1,
object2,
}
function getPropertyValue<
T extends keyof typeof objects
>(
objectName: T,
propertyName: keyof typeof objects[T]
) {
console.log(objects[objectName][propertyName]);
}
This function is now generic, which means it accepts a type. In this case it accepts the object name you want to use as T
, which any keyof
the objects
object.
Then the propertyName
argument uses whatever key that was to find out what properties should be allowed, by constraining the type to the properties of just one of those objects.
Now to get the data you drill into objects
by the name of a property on objects
, and then again on the specific property name.
Usage looks like this:
getPropertyValue('object1', 'property1') // 'foo'
getPropertyValue('object2', 'different1') // 'asdf'
getPropertyValue('object1', 'different1')
// Type error, 'different1' does not exist on 'object1'
See Playground
Object names
As Alex Wayne explains: Objects will only have names "as part of your data model."
Or in other words: An object's name is what you associate it with.
That association may e from within or from outside. Example:
// Name by self-definition
const someObject = { name: "self-named" };
// Name by association
const nameToObject = { "associated-name": {} };
Looking up by name
To avoid adding unnecessary properties to your objects, I'll use the "name by association" approach:
const objects = { object1, object2 };
Sidenote: In this shorthand object initialization, the identifiers are used as property names and their references as the properties' values.
With this, an implementation is straightforward:
const object1 = new class Object1 {
get property1() { return "foo"; }
get property2() { return "bar"; }
};
const object2 = new class Object1 {
get different1() { return "asdf"; }
get different2() { return "ghjk"; }
};
const objects = { object1, object2 };
// The implementation
function getPropertyValue(objectName, propertyName) {
return objects[objectName][propertyName];
}
const value = getPropertyValue("object1", "property2");
console.log(value);
Adding Typescript
To add proper typing, we need to think about how the output depends on the inputs. But also, how the inputs depend on each other.
A naive implementation may look like this:
function getPropertyValue(
objectName: "object1" | "object2",
propertyName: "property1" | "property2" | "different1" | "different2"
): any;
But this doesn't fully use what Typescript is capable of. Only a subset of propertyName
's original types can be used depending on objectName
's value: propertyName
depends on objectName
.
Since there is one dependency between function parameters, we need a generic parameter to link them.
We already know which value of objectName
corresponds to which type, thanks to our dictionary. We only need to restrict propertyName
to that type's keys:
type Objects = typeof objects;
function getPropertyValue<
K extends keyof Objects
>(
objectName: K,
propertyName: keyof Objects[K]
);
When calling this function, K
will be inferred from objectName
by default, and automatically restrict our options for propertyName
according to our typing.
Addendum
Name by identifier?
While (especially const
declared) variables can reference objects, their identifier –that is the variable name– is usually not considered their referenced object's name. For good reason!
You cannot easily look up objects through an identifier. To do so requires either new Function
or eval()
, which are known for having security issues.
Therefore, please see the following as a demonstration, not remendation:
const object1 = { name: "Object-1" };
const object2 = { name: "Object-2" };
const o = getObjectByName("object1");
console.log(o);
function getObjectByName(name) {
// You should avoid new Function!
return new Function(`return ${name};`)();
}
for...in
, for...of
, what for?
There are multiple for
loops:
- The regular (or simple)
for
-loop with an initialization, condition and incrementation block. - The
for...of
-loop to iterate through an iterable object. - The
for...in
-loop to loop over enumerable properties.
Make sure you familiarize yourself with these!
Tip: In many cases you can use Object.keys()
or .entries()
instead of a for...in
-loop, which avoids confusion with for...of
-loops.
Namespace pollution!
We don't want to instantiate objects
for every function call, which means it needs to be outside that function.
But naively moving it outside pollutes the namespace.
Instead, we can make use of a closure to instantiate it only once, but keep it hidden from the global scope:
// Mock imports
const object1 = { property1: "foo", property2: "bar" };
const object2 = { different1: "asdf", different2: "ghjk" };
const getPropertyValue = (() => {
const objects = { object1, object2 };
return function(objectName, propertyName) {
return objects[objectName][propertyName];
};
})();
const value = getPropertyValue("object1", "property2");
console.log(value);
console.log(objects);