Home > Mobile >  new object will be empty
new object will be empty

Time:01-10

I can't get this code to work.

import "reflect-metadata";

export class Castable {
  [key: string]: any;
  constructor(source: any) {
    console.log("source: ");
    console.log(source);
    Object.getOwnPropertyNames(source).forEach((propertyKey) => {
      const designType = Reflect.getMetadata("design:type", this, propertyKey);
      const customType = Reflect.getMetadata("custom:type", this, propertyKey);
      const type = customType !== undefined ? customType : designType;
      this[propertyKey] = this.convert(
        source[propertyKey],
        propertyKey,
        type,
        0
      );
    });
    console.log("after constructor this: ");
    console.log(this);
  }

  private convert(
    source: any,
    propertyKey: string,
    type: any,
    depth: number
  ): any {
    if (type === undefined) {
      return source;
    }
    switch (type.name) {
      case "Number":
        return Number(source);
      case "String":
        return String(source);
      case "Boolean":
        return source.toString() === "true";
      default:
        return new type(source);
    }
  }
}

/**  --- TreeRoot ---  */
export class MyConfigRoot extends Castable {
  result: boolean;
  count: number;
}

function init() {
  const json = '{"result":true, "count":32}';
  let input = JSON.parse(json);
  let newR = new MyConfigRoot(input);
  console.log("after new: ");
  console.log(newR);
}

init();

After getting the type with Reflect.getMetadata, type checking is performed. This code would result in an empty new object.

> node ./dist/test.js

source:
{ result: true, count: 32 }
after constructor this:
MyConfigRoot { result: true, count: 32 }
after new:
MyConfigRoot { result: undefined, count: undefined }

The assignment in constractor seems to succeed, but actually comes up empty. It is actually deeper than that, but it is a minimal structure to isolate the problem. Why would it be empty?

CodePudding user response:

This is caused by the useDefineForClassFields compilerOptions, or the lack of it. Because if not manually set, its default value is correlated to target option. From docs:

This flag is used as part of migrating to the upcoming standard version of class fields. TypeScript introduced class fields many years before it was ratified in TC39. The latest version of the upcoming specification has a different runtime behavior to TypeScript’s implementation but the same syntax.

This flag switches to the upcoming ECMA runtime behavior.

Default: true if target is ES2022 or higher, including ESNext, false otherwise.

Feel free to read the detailed backstory. Short explainer to your case goes like this:

/**  --- TreeRoot ---  */
class MyConfigRoot extends Castable {
  result: boolean; //            
  • Related