0

I have a class with static properties and I want to get a list of all property values:

class TimeRange {
    static ALL = 'all time'
    static MONTH = 'month'
    static WEEK = 'week'
    static DAY = 'day'
}

Now I want to get: ['all time', 'month', 'week', 'day']

Chris
  • 13,100
  • 23
  • 79
  • 162

3 Answers3

0

I have a feeling there are some edge cases not handled here but

class TimeRange {
    static ALL = 'all time'
    static MONTH = 'month'
    static WEEK = 'week'
    static DAY = 'day'
}

function getClassStaticProperties(constructor) {
  return Object.fromEntries(Object.entries(constructor));
}

function getOwnStaticProperties(instance) {
  return getClassStaticProperties(instance.constructor);
}


const tr = new TimeRange();

// via an instance
{
  const staticProps = getOwnStaticProperties(tr);
  console.log(JSON.stringify(staticProps, null, 2));
  console.log(Object.values(staticProps));
}

// via the class
{
  const staticProps = getClassStaticProperties(TimeRange);
  console.log(JSON.stringify(staticProps, null, 2));
  console.log(Object.values(staticProps));
}
gman
  • 100,619
  • 31
  • 269
  • 393
  • "I have a feeling there are some edge cases not handled here", maybe statics inherited by `extend`? – Kaiido Aug 31 '22 at 06:04
  • it doesn't see statics inherited by `extends` so seems to do what was asked, show "*own* static properties". It doesn't see static functions (which I was surprised by). My point about edge cases is I can probably do `TimeRange.foobar = 123` and it will start showing `foobar`. But then maybe `TimeRange.foobar` is an "own static". Or maybe if I hack the constructor with `Object.defineProperty` or something? – gman Aug 31 '22 at 06:34
  • Ah I missed the title asked for "own" statics only. Makes me curious as to when it's preferable to avoid inherited ones, but... ok, my bad. Arguably yes, `TimeRange.foobar` is now an "own static" property. – Kaiido Aug 31 '22 at 06:42
  • 1
    For the methods you could use `Object.getOwnPropertyDescriptors()`, but you'd also get things like `name`, `length`, and `prototype` which are also own properties of that class, but which I guess OP didn't really want. – Kaiido Aug 31 '22 at 06:56
0

I know it goes a bit beyond topic, but this is the closest i found in my research.

Like @Kaiido said, you may use Object.getOwnPropertyDescriptors() and delete the reserved properties if ancestors don't matter.

I struggled myself for class introspection and i hope it helps someone on a similar problem : ) .

class MyAncestor{
  static ancestorProp1='prop a 1';
}

class MyClass extends MyAncestor{
  static myclassProp1='prop c 1';
  // Ignored by Object.keys & Object.entries 
  static myclassMethod1(){ /*...*/ }
  constructor(){
    super();
  }
}

// An other way to declare a static property & method
MyClass.myclassProp2='prop c 2';
MyClass.myclassMethod2=function(){ /*...*/ };
// Ignored by Object.keys & Object.entries
Object.defineProperty(MyClass,'myclassProp3',{get:()=>'prop c 3'})

/**
  @param {class} constructor : the class constructor
  @param {boolean} [handleAncestor] : handle inherited classes
  @return {Array<string>} the static property names
*/
const getClassStaticPropertyNames=function(constructor,handleAncestor=false){
  const props=[];
  if(handleAncestor){
    const ancestor=Object.getPrototypeOf(constructor.prototype)?.constructor;
    if(ancestor&&ancestor.name!=='Object'){
      props.push(...getClassStaticPropertyNames(ancestor,true));
    }
  }
  const exclude=[
    // remove reserved properties
    'prototype','name','length',
    // remove inherited duplicates
    ...props
  ];
  
  return Object.getOwnPropertyNames(constructor)
  .filter(k=>!exclude.includes(k))
  .concat(props);

};

/**
  @param {class} constructor : the class constructor
  @param {boolean} [handleAncestor] : handle inherited classes
  @return {Object} the static properties
*/
const getClassStaticProperties=function(constructor,handleAncestor=false){
  const data={};
  getClassStaticPropertyNames(constructor,handleAncestor)
  .forEach(k=>data[k]=constructor[k]);
  return data;
}

console.log('Object.keys misses "myclassMethod1" & "myclassProp3" :',Object.keys(MyClass));

console.log('Object.entries misses "myclassMethod1" & "myclassProp3" :',Object.entries(MyClass).map(e=>e[0]));

console.log('MyClass static property & method names :',getClassStaticPropertyNames(MyClass));

console.log('MyClass & ancestors static property & method names :',getClassStaticPropertyNames(MyClass,true));

console.log('MyClass static properties & methods :',getClassStaticProperties(MyClass));

PS : I could have used Object.getOwnPropertyDescriptor() or Object.getOwnPropertyDescriptors() for a deeper description but the plunker would have been more complicated.

yorg
  • 600
  • 5
  • 7
-1

What's your use case here? If you're looking for a way to have some properties that you can iterate through but also refer to by key, then you can just use a normal object:

const timeRanges = {
  ALL: 'all time',
  MONTH: 'month',
  WEEK: 'week',
  DAY: 'day'
}
timeRanges.ALL; // 'all time'
timeRanges.MINUTE; // not allowed
Object.keys(timeRanges).map(key => timeRanges[key]); // ['all time', 'month', 'week', 'day']

Classes are not really designed to have their properties iterated through. However, if they absolutely must be in a class, you could turn them into instance properties follow the method outlined here:

class TimeRange {
  ALL = 'all time'
  MONTH = 'month'
  WEEK = 'week'
  DAY = 'day'
}
Object.getOwnPropertyNames(new TimeRange()); // ['all time', 'month', 'week', 'day'] 

That's somewhat of an anti-pattern, though.

One final option you might consider would be to use a string enum:

enum TimeRange {
  ALL = 'all time',
  MONTH = 'month',
  WEEK = 'week',
  DAY = 'day'
}
Object.keys(TimeRange).map(key => TimeRange[key]); // ['all time', 'month', 'week', 'day']

(To clarify, I'm assuming you're using Typescript here based on the tags in the question. If you're not, then the comments on your original question stand. You could still use options 1 and 2 that I suggest here, but obviously without the type checking benefits.)

ethan.roday
  • 2,485
  • 1
  • 23
  • 27
  • One use case which I'm currently struggling with is having the individual properties depend on one another. The example is I'm building a Class that has static members that build a store of API endpoints. For example, I have "/api1/", "/api2/" as two static members, and later, extensions of those endpoints are additional static members. You can't have these types of self references if you use an enum or an object, correct? – Adam P. Aug 21 '21 at 14:08
  • @AdamP. I think you're probably better off asking this as a separate question and outlining your situation in some more detail (preferably with an example) - it's hard to know what the right approach is given your comment. – ethan.roday Aug 23 '21 at 00:26