Typescript convert all date from interface to string

Viewed 968

Is it possible to transform all the Date types definition from my interface to string, as it get automatically transformed to string on JSON stringify.

interface Item {
   key: string;
   value: number;
   created: Date;
}

const item: Item = { key: 'abc', value: 1, created: Date() };

// convert to JSON
const itemJson = JSON.stringify(item);

// convert back itemJson to an object
const item2 = JSON.parse(itemJson);

// item2 is not of type `Item` as JSON.stringify convert Dates to strings
// so item2 is of type: { key: string; value: number; created: string; }

Would there be a kind of feature to transform the Date type from my interface to string? Something like const item2: ToJSON<Item> = JSON.parse(itemJson);

Note: I don't want to transform back item2.created to Date but I want to create a new interface corresponding to the conversion item to item2. So item is different of item2 and should stay different, therefor I need a new interface for item2. Of course, I could do it manually, but I have a bunch of interface to convert, I would like to do this with something similar to a utility type: https://www.typescriptlang.org/docs/handbook/utility-types.html

Note2: The goal is to get a new interface called Item2

interface Item2 {
   key: string;
   value: number;
   created: string;
}

Something like type Item2 = ToJSON<Item>.

4 Answers

TypeScript type system FTW:

interface Item {
  key: string;
  value: number;
  created: Date;
}

type SwapDatesWithStrings<T> = {
  [k in keyof(T)]: (T[k] extends Date ? string : T[k]);
}

type JsonItems = SwapDatesWithStrings<Item>;

// JsonItems is the same as:
// interface JsonItems {
//   key: string;
//   value: number;
//   created: string;
// }

It works deriving a generic type SwapDatesWithStrings from the base type T, with the same set of properties of T, but with a twist on the property type: properties deriving from Date are converted to strings.

In this case, 2 types are needed, that apart from the type annotation of a single field are otherwise identical.

Solution: Generic interface

Making the interface generic would allow you to parametrise it with type arguments, and re-use it across both of your types. See TypeScript generics for more.

Applying this on your example:

interface Item<T> {
    key: string;
    value: number;
    created: T;
}

const item: Item<Date> = { key: 'abc', value: 1, created: Date() };

// convert to JSON
const itemJson = JSON.stringify(item);

// convert back itemJson to an object
const item2: Item<string> = JSON.parse(itemJson);

Maybe consider declaring Item as a class. There are many libraries that help transforming plain object into class instances like e.g. class-transformer

Ok check this out its awesome, i got it from this dudes post How to handle ISO date strings in TypeScript?

So he's like you can add a revived function so lets apply it to your problem here:

interface Item {
   key: string;
   value: number;
   created: Date | string;
}

const item: Item = { key: 'abc', value: 1, created: Date() };

// convert to JSON
const itemJson = JSON.stringify(item);

// convert back itemJson to an object
// The juice is here, what you do is add the function as a second argument
const item2 = JSON.parse(itemJson, (key: any, value: any) => {
    // and in here you do your extra stuff...

    return Date.parse(value) ? new Date(value): value;

});

#Edit: To note, interfaces don't make it to JS, they get removed so if you want to acknowledge your interface potentially having a string type, then maybe define your interface like so:

interface Item {
   key: string;
   value: number;
   created: Date | string; // at least this way you are acknowledging a potential string type
}
Related