-1

I am trying to create a function that will instantiate an object of a given type along with nested properties if any. So came up with this method.

export function InitializeDefaultModelObject<T extends object> (): T  
{
    const root: T = {} as T;
    Object.keys(root).forEach(key =>
    { 
        if (typeof root[key] === 'object') root[key] =  {};
    });
    return root;
}

Problem is when I call it, it returns empty object {}, not an object of a type I am hoping to create.

    this.ProjectModel = InitializeDefaultModelObject<ProjectModel>();
    console.table(this.ProjectModel);

Is this a viable approach at all and what do I need to change to get desired results?

Victor
  • 13
  • 1
  • 2
  • `root` is an empty object, so `Object.keys` has nothing to iterate over – CertainPerformance Jul 27 '21 at 03:45
  • Thanks for replying. Yes, that is the problem - an empty object, cast is not working, hence a question. what needs to be changed to create empty object of type `T` instead in this case? – Victor Jul 27 '21 at 03:47
  • *what needs to be changed to create empty object of type T* doesn't really make sense. you can't create an instance of an object containing properties of a type you don't know – Liam Jul 27 '21 at 16:01
  • @Liam Right, you don't know compile time, but you do run-time. Guess, you can do it in C#, i.e `Activator.CreateInstance` but it's not feasible in Typescript. – Victor Jul 27 '21 at 16:24
  • `Activator.CreateInstance` would just do the same thing as your already doing. TBH the sentence *it returns empty generic object {}, not an empty object of a type I am hoping to create.* Doesn't make a lot of sense. What exactaly are you expecting this to do? – Liam Jul 28 '21 at 08:04
  • I think I see what your getting at. I'm pretty sure you want this: [Create a new object from type parameter in generic class](https://stackoverflow.com/questions/17382143/create-a-new-object-from-type-parameter-in-generic-class). Your trying to do something like (in C#) `new T();`? TBH [this works](https://stackoverflow.com/a/26696476/542251) pretty much the same as it would in C#. As you'd have to do ` where T: new()` to create an instance of `T` – Liam Jul 28 '21 at 08:12
  • @Liam Thanks for replying. Yes, that's the intent - instantiate object based on a generic type passed and use that object to initialize some default property values, like nested objects, as you can see from my snippet. This is to avoid ugliness of this `this.SomeModel = {configurations: {}, Users: {}, Patients: {} }`This would work in `.NET`. People downvote before even figuring out the intent, but maybe I have not stated that clearly. – Victor Jul 28 '21 at 14:19

1 Answers1

0

It looks like you're confusing your typings (i.e. from TypeScript) with your runtime code (i.e. JavaScript), and I think a big help for questions like this is understanding when one starts and the other ends. Remember, TS is really just meant to help you while developing. All of its typings and other special syntax might as well just be comments because your machine never sees them. In fact, after it compiles down to JS (which is the code that actually ends up running on your machine), it's usually going to look almost identical to how you wrote it, just with all the typings, generics, and other TS-specific tokens removed.

Speaking of which, here's what your code looks like when compiled down:

// my-file.js
export function InitializeDefaultModelObject() {
    const root = {};
    Object.keys(root).forEach(key => {
        if (typeof root[key] === 'object')
            root[key] = {};
    });
    return root;
}


// file-that-imported-my-file.js
this.ProjectModel = InitializeDefaultModelObject();
console.table(this.ProjectModel);

Especially if you already have experience with regular JS, the lack of generics or typings here may help you better understand why things aren't working. As is, this function neither expects nor is given any parameters, and as a result, it's always going to have the same output (i.e. an empty object).

However, writing a highly scalable solution that works for objects of any type across all scenarios is not straightforward unless your use case is extremely limited. For example, here's an attempt that kinda-sorta-maybe works based on your original code (in JS, which means it works in TS too even if the compiler complains):

export function InitializeDefaultModelObject(originalObj) {
    var sortaCopiedObj = {};
    Object.keys(originalObj).forEach(key => {
        var typeOfField = typeof originalObj[key];
        if (typeOfField === 'object') sortaCopiedObj[key] = {};
    });
    return sortaCopiedObj;
}

This will work if and only if the objects you're passing in are literals (i.e. so they don't have a prototype to worry about inheriting), you don't care about the value of non-object fields, and you don't care about the nested fields of the object properties that you do copy. For example:

var obj1 = { a: {} };
var copy1 = InitializeDefaultModelObject(obj1); // { a: {} } You probably expect this

var obj2 = { a: { innerProperty: {} }, b: "hey!" };
var copy2 = InitializeDefaultModelObject(obj2); // { a: {} } But did you expect this?

To fix that, you can add an else statement to your function to account for values other than objects, and you can also recursively call it to continue building nested object properties. However, that doesn't solve things if prototypes are involved, and I'm concerned you might not be fully aware of that. If you really only want to use this function to copy object literals of arbitrary depth, cool, then go ahead and add those fixes I just mentioned, but there may be an easier way. If you're going to instantiate an object of known structure multiple times... why not just make that object into a class?

class MyClass {
    a = { innerProperty: {} };
    b = "hey!";
}

// All of these have the same properties, but they're all unique instances
var x = new MyClass();
var y = new MyClass();
var z = new MyClass();
user3781737
  • 932
  • 7
  • 17
  • Thank you for the reply, appreciated! To answer your question, I do have a class like that(many classes), and I am trying to write method that would instantiate any of those classes of a given type. So In C# terms it would look something similar to this: `public T InitializeDefaultModelObject() where T: new(){ return new T(); }` while your example would sorta clone an existing object, not instanciate a new one. Based on what you said this is just not supported in TS? – Victor Jul 28 '21 at 20:04
  • @Victor my last example does instantiate the class, three separate times. – user3781737 Jul 28 '21 at 20:10
  • What if those are TS interfaces, not classes? Think I can go by this, thank you. – Victor Jul 28 '21 at 20:14
  • @Victor You’ll need to have these exist as classes as well if you want to go that route, as interfaces are TS-only and don’t exist at runtime, while classes do. – user3781737 Jul 28 '21 at 22:05
  • Yeah, figures. Unfortunately have to deal with existing nested interface hierarchy, nothing can do about that. – Victor Jul 28 '21 at 22:35