3

I have created a function that does the runtime requirement of checking for null and undefined not being true:

 function hasFields<T>(obj: T | T[], ...fields: (keyof T)[]): boolean {
    const inObj: { (obj: T): boolean } = (obj) => fields.every((f) => obj[f] != null);

    if (Array.isArray(obj)) {
        return obj.every((o) => inObj(o));
    } else {
        return inObj(obj);
    }
}

But what I would really like is something that would either return an obj with an updated type, or being able to use this in an if statement and be able to get the types updated within the context of the if statement.

I have seen questions like this: Typescript type RequireSome<T, K extends keyof T> removing undefined AND null from properties but it doesn't do it for a list of fields.

If it helps, the fields are known at compile time.

1 Answer 1

6

The type RequireAndNotNullSome is usable as is for your use case as well. To convert hasFields to a type guard you need to add a type parameter (lets call it K) to capture the actual keys the function was called with. Also to get it to work for both arrays and object types, you will need separate overloads for those casses:



type RequiredAndNotNull<T> = {
    [P in keyof T]-?: Exclude<T[P], null | undefined>
}

type RequireAndNotNullSome<T, K extends keyof T> = 
  RequiredAndNotNull<Pick<T, K>> & Omit<T, K>

function hasFields<T, K extends keyof T>(obj: Array<T | RequireAndNotNullSome<T, K>>, ...fields: K[]): obj is Array<RequireAndNotNullSome<T, K>>
function hasFields<T, K extends keyof T>(obj: T | RequireAndNotNullSome<T, K> , ...fields: K[]): obj is RequireAndNotNullSome<T, K>
function hasFields<T, K extends keyof T>(obj: T | Array<T>, ...fields: K[]): boolean{
    const inObj: { (obj: T | RequireAndNotNullSome<T, K>): boolean } = (obj) => fields.every((f) => obj[f] != null);

    if (Array.isArray(obj)) {
        return obj.every((o) => inObj(o));
    } else {
        return inObj(obj);
    }
}

type Question = {
    id: string;
    answer?: string | null;
    thirdProp?: number | null;
    fourthProp?: number | null;
}

declare var v: Question;

if (hasFields(v, "answer", "thirdProp")) {
  v.answer.anchor // string 
}

declare var arr: Question[];

if (hasFields(arr, "answer", "thirdProp")) {
  arr[0].answer.anchor // string 
}

Playground Link

Sign up to request clarification or add additional context in comments.

1 Comment

This is really good, thanks! I have to admit I understand C++ Template Metaprogramming better than this stuff. So I am going to have to take a hard look at this wizardry.

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.