0

I have those TypeScript classes in the same .ts file because there are import dependencies. I would appreciate if someone could help me refactor the code so I could remove the circular reference of imports:

The first one is the abstract GenericModel :

export abstract class GenericModel {
    nodeClass: string;
    id: string = "0";

    static fromJson(json: any): GenericModel {
        if (json.nodeClass === "Entity") {
            return EntityModel.fromJson(json);
        }
        else if(json.nodeClass === "User") {
            return UserModel.fromJson(json);
        }
        return null;
    }
}

The other two classes are EntityModel(listed below) and UserModel:

export class EntityModel extends GenericModel {
    nodeClass: string = "Entity";
    alias: string;
    description: string;

    constructor(public text: string, public id: string, public uuid: string, alias: string, public rand:string = "") {
       //[...]
    }

    //instance methods
    //[...]

    public static fromJson(json: any): EntityModel {
        var entity = new EntityModel(json.text, json.id, json.uuid, json.alias, json.rand);
        entity.description = json.description;

        if (json.types) {
            for (let type of json.types) {
                let objectifiedType: EntityModel = EntityModel.fromJson(type);
                entity.types.push(objectifiedType);
            }
        }
        if (json.innerEntities){
            for (let innerEntity of json.innerEntities) {
                let objectifiedInnerEntity: EntityModel = EntityModel.fromJson(innerEntity);
                entity.innerEntities.push(innerEntity);
            }
        }
        return entity;
    }
}

What I am doing here is deserialization of JSON using a hierarchy of static calls fromJson() based on nodeClass.

It is obvious that if GenericModel was in a separate file it would need to import EntityModel and UserModel while the other two if they were in separate file they would need to import GenericModel.

GenericModel --- has to import --> EntityModel, UserModel

EntityModel --- has to import --> GenericModel

UserModel --- has to import --> GenericModel

I wonder if there is a way to refactor the code so it does what it does now but also the classes are in separate .ts files.

Thank you!

Michail Michailidis
  • 11,792
  • 6
  • 63
  • 106

1 Answers1

2

The trick here is to isolate the circular dependencies into their own module. I would extract all the fromJson methods into a single, new module. Then I would convert fromJson into a class called something like ModelFactory, since this is now more similar to a factory pattern. So, the end result would look something like this:

export class ModelFactory {
  // maybe add a constructor for some configuration data, maybe not

  create(json: any) {
    ...
  } 
}

Now, I also see that you type the json object as any. That seems to be bit broad when you seem to know at least a few properties on the type. I'd try to create an interface for the json object, like this:

export interface ModelJson { // find a better name
  text?: string; 
  id?: number;
  uuid?: UUID; 
  alias?: string;
  rand?: number;
  ...
}

type UUID = number;

This is a more typescripty way of doing things.

Andrew Eisenberg
  • 28,387
  • 9
  • 92
  • 148
  • Thanks! I had the feeling I would need to add a new class like the factory you suggested! Thanks for the tip of the interface/json :) Btw what is ```type UUID = number;``` at the end? Happy New Year! – Michail Michailidis Dec 31 '16 at 20:41
  • 1
    That is just a type alias. When you have something like a UID that happens to be a number, but really it's just a technical detail and it has its own semantics, you often use a type alias. This ensures that you can only use UIDs where expected and you won't accidentally pass in a vanilla number. – Andrew Eisenberg Dec 31 '16 at 21:47
  • for me the ```uuid``` is a string though! Can I still do: ```type UUID = string;```? Thanks – Michail Michailidis Dec 31 '16 at 21:51
  • 1
    Of course. More information about type aliases are found here: http://www.typescriptlang.org/docs/handbook/advanced-types.html In general, I mostly use type aliases for better documentation of parameters and properties. They are never fundamental to the application. – Andrew Eisenberg Jan 02 '17 at 14:10