2

Given a type how can I write a recursive mapped type that yields a type that is with all the same keys but with their types being strings instead of whatever their incoming type is? Specifically, I want to handle nested objects & arrays.

type MySourceType = {
  field1 :string,
  field2: number,
  field3: number[],
  field4: Date, 
  field5: {
    nestedField1: number,
    nestedField2: number[]
    nestedField3: Date,
  }
}
type MyDestinationType = MakeAllFieldsString<MySourceType>;

should yield:

type MyDestinationType = {
    field1 :string,
    field2: string,
    field3: string[],
    field4: string, 
    field5: {
      nestedField1: string,
      nestedField2: string[]
      nestedField3: string,
    }
  }

this works for a regular "flat" object but fails to handle the nested objects and arrays

type JsonObject<T> = {[Key in keyof T]: string; }

I also tried this but it didn't seem work do what I expected either.

type NestedJsonObject<T> = {
[Key in keyof T]: typeof T[Key] extends object ? JsonObject<T[Key]> : string;
}
3
  • What is that “typeof” doing in your second try? Isn’t there an error there? Does it start working if you just remove typeof? Commented Nov 29, 2021 at 18:11
  • good catch; I was trying different variations and that is indeed an error. It doesn't work as expected without the typeof though...or at least doesn't do what I hoped it would Commented Nov 29, 2021 at 19:02
  • Can you edit your question not to include syntax errors so that your example becomes a minimal reproducible example? Also, since Date is an object type that you want to become string but {a: number} is an object type that you don't want to become string, could you explain how you want the mapping to tell the difference between object types that you want to recurse into versus ones that you don't? Like, we could special-case Date, but is that the only one? Commented Nov 29, 2021 at 19:17

1 Answer 1

1

You could do like this:

type MySourceType = {
  field1: string;
  field2: number;
  field3: number[];
  field4: Date;
  field5: {
    nestedField1: number;
    nestedField2: number[];
    nestedField3: Date;
  };
};

type Literal = { [k: string]: {} };

type JsonObject<T> = T extends Array<infer U>
  ? U extends Literal
  ? Array<JsonObject<U>>
  : Array<string>
  : T extends Literal
  ? { [Key in keyof T]: JsonObject<T[Key]> }
  : string;

type MyDestinationType = JsonObject<MySourceType>;

TypeScript playground: https://tsplay.dev/WYBlgw

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

10 Comments

Thanks! Two questions - what does the type Literal there do? and second question - array of objects turn into arrays of strings; is there a fix for that?
Your Literal check is a bit strange, since this only happens to work because MySourceType is a type alias of an anonymous object type and not an interface. Do you think OP wants this to happen if an interface is passed in? And also, Literal won't match anything with values that are nullable, so this happens. Not sure if this is the desired behavior here.
I've provided a fix for the arrays @jcalz what do you suggest as a better solution?
@James please edit the question to demonstrate use cases you care about so that the code there is a minimal reproducible example. If "array of objects" is something you have a requirement for, you should show what it is so that people answering don't make assumptions that turn out to be incorrect.
@GuerricP without details from OP about what they expect to happen in different edge cases I'm not sure. Apparently the version you posted is what they want because it's been accepted, but I do wonder what they want to see in the cases I highlighted above.
|

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.