1

I'm trying to define a query object dynamically (which is used for some MongoDB method calling).

let query = {
  category: { $exists: true, $ne: [] }
}

if (isAdmin) {
  query = {
    category: { $exists: true },
    isFinalized: { $exists: false }
  }
} else if (isEditor) {
  query = {
    category: { $in: specialisation }
  }
}

It is only a small part of the object generation. But with that I do get the TS error Object literal may only specify known properties for isFinalized and category.$in.

So how should I handle this correctly?

let query = {
  category: { $exists: true, $ne: [], $in: undefined },
  isFinalized: undefined
}

This seems very ugly to me.

4
  • Does query have a type or is Typescript complaining about a random object? Commented Jan 20, 2021 at 16:04
  • Try to create union type and assign it to query Commented Jan 20, 2021 at 16:06
  • I guess you wouldn't want to define a type for query explicitly either in that case? Commented Jan 20, 2021 at 16:23
  • No. Query is not typed as it has very different structure... Commented Jan 20, 2021 at 16:48

2 Answers 2

1

A Factory like class could be quite helpful when you separate roles out, allow different parts of the object to build separately based on the role and then put them together using 1 final public method:

enum Roles {
  Default = "Default",
  Admin = "Admin",
  Editor = "Editor"
}

interface ICategory {
  $exists?: boolean;
  $ne?: any[];
  $in?: string;
}

interface IIsFinalized {
  $exists: boolean;
}

interface IQuery {
  category: ICategory;
  isFinalized?: IIsFinalized;
}

class QueryBuiler {
  constructor(private role: Roles = Roles.Default) {}

  public getQuery(): IQuery {
    return {
      category: this.getCategory(),
      isFinalized: this.getIsFinalised()
    };
  }

  private getCategory(): ICategory {
    switch (this.role) {
      case Roles.Admin:
        return { $exists: true };
      case Roles.Editor:
        return { $in: "specialisation" };
      default:
        return { $exists: true, $ne: [] };
    }
  }

  private getIsFinalised(): IIsFinalized | undefined {
    if (this.role === Roles.Admin) {
      return { $exists: false };
    }
  }
}

console.log(new QueryBuiler(Roles.Admin).getQuery());
Sign up to request clarification or add additional context in comments.

5 Comments

Answers with no explanation are rarely upvoted when there are answers with explanation...
@HereticMonkey Although it is a great solution ;-)
sorry but I thought the code was quite self explanatory. I can add some text
What if I need to remove a field? Assume there should not be a category field, if user is an admin, so there is no category in the result query...
make category optional too using ? and return it only when you like. Then in getCategory for case Roles.Admin return undefined and amend the return type to also have | undefined.
0

Define a type with some optionals:

type Query = {
  category: {
    $exist?: boolean,
    $in?: any,
    $ne?: Array<string>,
  },
  isFinalized?: {
    $exists?: boolean;
  }
};

let query: Query = {
  category: { $exists: true, $ne: [] }
} as any;

if (isAdmin) {
  query = {... query,
    isFinalized: { $exists: false }
  };
} else if (isEditor) {
  query = { ...query,
    category: { $in: specialisation }
  }
}


Or the ugly one. Just use as any:

let query = {
  category: { $exists: true, $ne: [] }
}

if (isAdmin) {
  query = {
    category: { $exists: true },
    isFinalized: { $exists: false }
  } as any;
} else if (isEditor) {
  query = {
    category: { $in: specialisation }
  } as any;
}

In your code you create an element, then overwrite it directly (if or else). The first definition is useless. This way you prevent it:

let query;

if (isAdmin) {
  query = {
    category: { $exists: true },
    isFinalized: { $exists: false }
  }
} else if (isEditor) {
  query = {
    category: { $in: specialisation }
  }
}

Or: You can use the spread operator to extend the object (if possible) and use as any on the first declaration:

let query = {
  category: { $exists: true, $ne: [] }
} as any;

if (isAdmin) {
  query = {... query,
    isFinalized: { $exists: false }
  };
} else if (isEditor) {
  query = { ...query,
    category: { $in: specialisation }
  }
}

2 Comments

I used the first definition as default value. In some cases this default object has to be modified or extended.
When you don't know all the possible parts, you have to use any. Think there is no way around.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.