2

Transferred js typescript, faced with the problem. The following function works with two types of data, I see this error:

Property 'dateTime' does not exist on type 'Operation | OperationCreated'. 
Property 'dateTime' does not exist on type 'OperationCreated'

type DateTime = {
  date: string;
};

type Operation = {
  dateTime: DateTime;
};

type OperationCreated = {
  createdDate: string;
};

const sortByDate = (o1: Operation | OperationCreated, o2: Operation | OperationCreated) =>
  stringToMillisecond(o1.createdDate || o1.dateTime.date) - stringToMillisecond(o2.createdDate || o2.dateTime.date);

Making the first steps in typescript, please help

3 Answers 3

5

TypeScript does not perceive o1.createdDate || o1.dateTime.date as valid for a few reasons:

  • The biggest one in terms of runtime impact is that you presume that o1.createdDate will only be falsy if it is undefined. But the empty string is also falsy. If someone calls this:

    sortByDate({ createdDate: "" }, { createdDate: "" });
    

    then you will end up calling stringToMillisecond(undefined), which I would suspect is an error. So I would give up on using a falsy check on possibly-string values here.

  • The TypeScript compiler does not let you read a property value of a union type unless every member of the union contains a (possibly optional) property with that key. So o1.createdDate is an error unless you can narrow o1 to a OperationCreated first, by differentiating between the possible values. One way to do this is to use the in operator, like:

    "createdDate" in o1 ? o1.createdDate : o1.dateTime.date; // no error
    

    This works because the compiler uses the in check to narrow o1 to OperationCreated in the "then" clause of the ternary operator, or to OperationDefault in the "else" clause.

So the most straightforward fix I could imagine here is:

const sortByDate = (
  o1: Operation | OperationCreated,
  o2: Operation | OperationCreated
) =>
  stringToMillisecond("createdDate" in o1 ? o1.createdDate : o1.dateTime.date) -
  stringToMillisecond("createdDate" in o2 ? o2.createdDate : o2.dateTime.date);

Okay, hope that helps. Good luck! Link to code

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

Comments

1

This error is because the compiler doesn't know if o1 and o2 are Operation or OperationCreated. You may use discriminated unions.

My solution (to clean)

interface DateTime {
  date: string;
}

interface OperationDefault {
  type: 'default';
  dateTime: DateTime;
}

interface OperationCreated {
  type: 'created';
  createdDate: string;
}

type Operation = OperationDefault | OperationCreated;

const sortByDate = (o1: Operation, o2: Operation) => {
  const d1 = o1.type === 'default' ? o1.dateTime.date : o1.createdDate;
  const d2 = o2.type === 'default' ? o2.dateTime.date : o2.createdDate;

  return stringToMillisecond(d1) - stringToMillisecond(d2);
};

3 Comments

What are you going to do if you have 5 types of data? Are you going to add 'if, if' if if if?
If there is 5 types of data, and there is no common fields well yes, you can't guess where is the wanted data. In your case a switch may be more appropriate, I just put a quick solution that show the concept of discriminated unions which is a good solution for the author issue.
But if you have a better solution @Dezigo I'm really open to see it (no irony).
-1

Use an interface instead of a type literal

    interface DateTime {
        date: string;
    }

    interface Operation {
        dateTime: DateTime;
    }

    interface OperationCreated {
        createdDate: string;
    }

    const sortByDate = (
        o1: Operation | OperationCreated,
        o2: Operation | OperationCreated
    ) =>
        stringToMillisecond((o1 as any).createdDate || (o1 as any).dateTime.date) -
        stringToMillisecond((o2 as any).createdDate || (o2 as any).dateTime.date);

5 Comments

Cast to any and remove the typing cannot be a good solution :(
Interfaces are generally preferred over type literals because interfaces can be implemented, extended and merged.
Ok I don't criticize the choice of using interface instead of types, I'm Ok with that. What I don't like is (only) the use of as any which destroy the typing.
What if I add an other Operation to possible ones in the function ? Even if this new type is not handled, there will be no errors from the compiler.
Any is a solution for a mixed types, you just need to use it carefully. Otherwise, there are many different ways how to it

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.