TypeScript Wizardry: Recursive Template Literals

Поделиться
HTML-код
  • Опубликовано: 26 сен 2024
  • In this one, I'll show you some template literal magic for accessing deeply nested objects in TypeScript. If you like pushing the boundaries of type-level programming, this video is for you!

Комментарии • 137

  • @sstur
    @sstur  Год назад +7

    The code from this video can be found here: bit.ly/iuebsku

  • @YilmazDurmaz
    @YilmazDurmaz Год назад +72

    I use types all the time (many languages) but I was not even aware "type-level programming" is a thing :)
    TypeScript is at another level.

    • @Microphunktv-jb3kj
      @Microphunktv-jb3kj Год назад +8

      thats why javascript/typescript sucks... initially it feels cool and easy language.. but the learning curve goes up and up overtime, the more complicated ur project will become...
      but in lower lvl languages, the learning curve is upfront and will not go up overtime and hard things are easy to do, because they have proper standard libraries

    • @W1ngSMC
      @W1ngSMC Год назад +15

      You haven't done template meta programming in C++ then (or Rust macros). I envy you.

    • @tsukinoko_kun
      @tsukinoko_kun Год назад +8

      I like this in TypeScript. It is very useful for library code. But you have to do unit tests to ensure that your JavaScript code does the same thing as your types say.

    • @sstur
      @sstur  Год назад +3

      @@tsukinoko_kun Yes, this is a good point! After you get the types working you have to get the implementation working (or the other way around) and then you need to make sure they stay in sync. It really is like two languages in the same source code!

    • @oscarcarlen5952
      @oscarcarlen5952 Год назад +1

      @@sstur Zod is your friend

  • @confused_horse
    @confused_horse Год назад +5

    I was casually watching this in the living room and was really excited because it's just extraordinarily good content. But as soon as I was done I turned around to see my girl friend judging me. She called me a nerd and now I am happy in two ways. :>
    Thank you for sharing this and "yolo"

  • @brigadafitness5833
    @brigadafitness5833 Год назад +28

    Great content man, I think this type of advanced ts videos are really valuable, and it's not something you find often (at least with this quality). Keep the great job.

    • @sstur
      @sstur  Год назад +1

      Thanks! I really do like that folks are into this advanced TS stuff. Have more vids coming soon!

  • @arogueotaku
    @arogueotaku Год назад +4

    Damn dude. That big brain move of intersecting string with the T[K] blew my mind. This video is going straight to my favorites playlist. You got a sub from me.

  • @daniellynch3724
    @daniellynch3724 Год назад +2

    This is amazing black magic and I’m glad I watched the video.
    If I ever saw anyone submit anything remotely similar to this in a PR it would get a Deny so fast my left click would break the sound barrier.

  • @gnarusg8708
    @gnarusg8708 Год назад +10

    Incidentally I needed something like this for work a few weeks ago. Found some stuff on Stack overflow, but this is the easiest solution to this problem I've found. Thank you.
    Only way this video could be better is if you addressed how to have multiple "stop" value types, string in this example, like string | number | string[], but then infer each type from the multiple addressed to right path key. Hopefully that made sense. I don't even know how possible that is but I'm curious to know.
    It's rare that RUclips recommends video this well. Subscribed. Great video!

  • @rahul38474
    @rahul38474 4 месяца назад

    I did something similar for another project, I made K extends string the outermost ternary operation so that in the true branch I didn’t need to do K & string, as in that branch K is narrowed to some string type. I used this type (I called it dot path) to map keys of one type to keys of another type based on a suffix in each key. Type level programming is my favorite feature of TypeScript (also probably the only feature I like tbh) because it feels like doing a proof, and also because the literal types make for a really good developer experience since the LSP can suggest strings from that type.

  • @Athet0s1s
    @Athet0s1s 9 месяцев назад +1

    I actually used this in our company to name the fields in the api responses as we get Keys that are in objects or arrays, and we use them parsed in a specific way (with __ instead of .) and It was so difficult for me to manage to do this. I think this is greatly explained and would've been so Happy to find this video when I did this 😂

  • @maximemondello6174
    @maximemondello6174 6 месяцев назад

    This video is Amazing ! I needed a lite translation hook for my project and didn't want to fiddle with libraries for it. This is just perfect! I adapted it to be able to loop trough several files of translate keys and it works wonderfully !
    Thank you for that!

  • @SecularDarwinism
    @SecularDarwinism Год назад +1

    This is definitely my jam. Have stumbled upon many of these, what appeared to me to be roadblocks, glad to see they are traversable.

  • @yuukidename403
    @yuukidename403 Год назад +10

    I know this is mainly a demonstration of advanced TS features, and it also helps when having to write type declarations for existing libraries. And sometimes it's the kind of API you want to have.
    But for new code, personally I would think about keeping it simple and just use e.g. t().greetings.morning instead of t("greetings morning"), where t = l => locales[l] (which is even one character shorter).
    This way no recursion is required and you get the same autocomplete suggestions. And you'll also get the exact type at given key for free, which somebody in the comments suggested could be used parameters etc.

    • @seftikara651
      @seftikara651 Год назад +1

      true, especially if everything is statically hard-coded within the app.
      but, maybe, the video approach, by using parameterized input, would be useful if the localization (in this case) are not entirely hard-coded within the program and could be imported from external sources, and also if that each keys are not strictly required.
      so that, you can define the default locale and its utilities just as in the video to provide code completion, etc., and freely plug optional locale after. And any not-found keywords from optional locales will fallback to the default one, without the need of implementing null-check (on each calls), etc.
      this also means that, if this is a web app, all locales doesn't have to be bundled to the main app for the user to use. The app could fetch it separately as needed.

    • @aenguswright7336
      @aenguswright7336 Год назад +1

      The trouble with this approach is that in the case of internationalization, it would be much harder to write a fallback case since you have to know at the time that you fetch the object tree if something will fall back, rather than at the time of access. This would mean some complex recursive function to fetch fallbacks at time of fetch, which you would have to pay every time you called the function at runtime, rather than at worst only on the occasions you tried to access a node which didn't exist and at best, only in development.

    • @EverRusting
      @EverRusting 10 месяцев назад

      I get that but i18n libraries usually work with path strings...

  • @tezismith8795
    @tezismith8795 9 месяцев назад

    great explanation! I didn't know about that `K & string` trick, very useful :) thanks

  • @eltyo340
    @eltyo340 Год назад +1

    14:00 I'm glad I'm not the only one who writes "yolo" when writing dummy data/console logs

  • @Jamiered18
    @Jamiered18 Год назад +1

    Ah, excellent! When I came upon this problem years ago, this was not possible yet in Typescript. I had to solve the problem in a more janky way. But this is so simple and sensible now, I can rewrite it nicely, thank you

  • @aram5642
    @aram5642 Год назад +3

    Fantastic! Just in time for me, but I also love the way you reason about it.

    • @sstur
      @sstur  Год назад

      Thanks Ar Am!

  • @webbae
    @webbae Год назад +13

    Cool video Simon. I learned a lot just in the first 5 min. Had to rewind a few times in there hehe. Audio sounds great too!

    • @sstur
      @sstur  Год назад +1

      Thanks man! Appreciate you checking it out!

  • @trongvinhnguyen6366
    @trongvinhnguyen6366 Год назад

    I have though about this many times and though it was impossible in TS.
    Now you've clear my mind.
    Thank you!

  • @dolevgo8535
    @dolevgo8535 Год назад +5

    Great video!
    One thing I would've done differently(and i'd love to hear your opinion) is- instead of manipulating the key of the mapped type, I would do the key-mapping-shenanigans you did in the values of the mapped type, then indexed using [keyof T]. it would probably be cleaner, using both sides of the object, and i also think you wouldn't need the second intersection at 11:50 since the return type wouldn't be a PropertyKey, rather a string(as the values would only be strings)
    its a slight knitpicking, would love to hear your opinion
    still, loved seeing you going through the problem, looking forward to more content!

    • @sstur
      @sstur  Год назад +3

      Thanks Dolevgo! I believe you're right that I could have used the value side of each property instead of mapping the key. Actually, before we had "key remapping" (prior to TS 4.1) we had to do it using the value side. I'm not 100% sure that would eliminate the need for `& string` but I should poke around and see what I come up with and post back here. Thanks!

  • @ionitaa
    @ionitaa Год назад +3

    Imagine geting hired somewhere and your first task is to debug this while Simon is no longer working there

    • @sstur
      @sstur  Год назад +3

      😂 You've legit got a point here

  • @bamboo6044
    @bamboo6044 Год назад

    I agree with most comments, I would like to see more TS content, very interesting and useful!

  • @TheTom265
    @TheTom265 8 месяцев назад

    Brilliant. Thank you!!

  • @0xramon
    @0xramon Год назад +2

    been playing around with something similar, ended up with
    type DotNotation = {
    [K in keyof T]: T[K] extends object
    ? `${K & string}.${DotNotation}`
    : K & string;
    }[keyof T];
    type Foo = DotNotation

    • @0xramon
      @0xramon Год назад +1

      can also extend it to filter out certain keys, for example
      ```
      type Excluded = "sample" | "other";
      type DotNotation = {
      [K in keyof T]: T[K] extends object
      ? `${K & string}.${DotNotation}`
      : `${K extends Excluded ? never : K & string}`;
      }[keyof T];
      const x = {
      foo: "a",
      sample: "unreachable",
      bar: {
      other: "unreachable",
      zoo: "b",
      moo: "c",
      },
      };
      type Foo = DotNotation;
      ```
      only `"foo", "bar.zoo", "bar.moo"` are available

  • @jeromealtariba7339
    @jeromealtariba7339 Год назад +2

    these types might be very useful when creating sql queries, using nested relations, with an ORM such as typeorm. Very interesting thks

  • @paulborek9163
    @paulborek9163 Год назад +8

    Wow I am truly amazed by this video. I considered myself as a mid advanced typescript user, but I just never got my brain that far... hoping to see much more typescript from you. Great work there

  • @EconomicsDomain
    @EconomicsDomain 7 месяцев назад

    Great explanations, but personally would frown if I saw this in a project. Mental gymnastics and cognitive overload ;)

  • @voyager_ll
    @voyager_ll Год назад

    Wow, that's crazy :D didn't know that was possible!

  • @JesseBaker121
    @JesseBaker121 Год назад

    Great video, localws are always a pain and I've had this issue in the past. Very elegant solution

  • @InMemoryOfNeo
    @InMemoryOfNeo Год назад

    Awesome 👌👏👏👏👏👏👏👏👏

  • @MinorMood
    @MinorMood Год назад

    Man, very good, thanks for sharing! With the TS one actually have a huge boilerplate even before actually start coding - but it is definitely worth of that!

  • @tosunabi1664
    @tosunabi1664 Год назад

    Piece of art for a new TS user.

  • @benllshua
    @benllshua Год назад +1

    wow cool Utility type!!

  • @TheRhopsody
    @TheRhopsody Год назад +1

    This is amazing insight to me. Thank you. 😊

  • @chocolatebarcroissant
    @chocolatebarcroissant Месяц назад

    Sick vid man!

  • @simpel8040
    @simpel8040 Год назад +1

    That was awesome!

    • @sstur
      @sstur  Год назад

      Thanks Simpel!

  • @76Freeman
    @76Freeman Год назад

    Wow great stuff. You've just gained a new subscriber :).
    I've been getting more and more into Typescript and I often struggle with recursion. I think the "variables" name in types aren't always the best :) things like K and T aren't very descriptive and it makes it really hard to understand what is going on :). I think one nice thing would be to comment the most challenging parts so you know what is going on.
    But I loved your process though, thank you very much the great video.

  • @cipherxen2
    @cipherxen2 Год назад +54

    Caution: python junkies should not watch this video, it might break their brain

    • @nieczerwony
      @nieczerwony Год назад +3

      Tak Python anytime over any shit from MS.

    • @MirrorsEdgeGamer01
      @MirrorsEdgeGamer01 Год назад +1

      @@nieczerwony Microsoft is helping the Python team to increase speed.

    • @nieczerwony
      @nieczerwony Год назад +1

      @@MirrorsEdgeGamer01 Well as long as they don't own it.

  • @sourishdutta9600
    @sourishdutta9600 Год назад +1

    Nice one. Create more like this. 👍

  • @kylclrk
    @kylclrk Год назад

    This is fantastic. Keep it up!

  • @LeshkaSaD
    @LeshkaSaD Год назад

    very educational video 👍 would be great to see more videos about complex types in TS and you find a solution for them

  • @Vedmalex
    @Vedmalex Год назад

    Thanks for the video. Trying do something lake this without using recursive type… this solution cooler!

  • @artemkaratai8958
    @artemkaratai8958 Год назад +1

    subscribed and waiting for more :)

  • @Giffeln
    @Giffeln Год назад

    Great video!
    My version for this case would be
    type PathTo = keyof {
    [K in keyof T as T[K] extends Record
    ? `${K & string}.${PathTo & string}`
    : K & string]: any;
    };

  • @LucasPachecoF
    @LucasPachecoF Год назад

    Dude, that’s awesome. Definitely will help me out

    • @sstur
      @sstur  Год назад

      Thanks Lucas!

  •  Год назад +1

    Didn't know you could put string literals to generate keys! I was always wondering, how react solved "data-xyz" props. I was looking into its types, but couldn't find it there.. now I know it is some version of this!

  • @HACKERMORE
    @HACKERMORE Год назад +1

    "T[K]" I'm sure people will try to backtrack a lot to understand this. It's still a pain in the ass 🤣🤣

  • @Cinerable
    @Cinerable Год назад

    Great video! Thanks!

  • @WorstmetJus91
    @WorstmetJus91 Год назад +1

    Very cool! React-hook-form I believe uses exactly this syntax to access form data but i do not know how they made the typing work. Perhaps worth the look? I know I will now!

  • @raymondmichael4987
    @raymondmichael4987 Год назад

    Thanks buddy 😮, this got me;
    Still getting my feet wet with Typescript 😊

  • @DJ_Locks
    @DJ_Locks Месяц назад

    Oh hey, the thing I've spent the last 3 weekends trying to do...

  • @CottidaeSEA
    @CottidaeSEA Год назад

    This is honestly really cool. I just wish TypeScript wasn't JavaScript, because in the end you can just throw anything into whatever TypeScript function is created and watch it break. I know many who struggle with TypeScript due to IDE performance as well, which is unfortunate. More of an IDE problem though (and likely relying too much on inference).

  • @ArashMotamedi
    @ArashMotamedi Год назад

    Awesome! Magic!

  • @nestnik
    @nestnik 5 месяцев назад

    That's amazing! What resources can I use to level up my type-level programming skills?

  • @dokkenrox
    @dokkenrox Год назад +1

    Man, that's rough! LOL It's an elegant solution but it's not very legible. It seems like once we get to the level of complexity where we need to invoke the term "type-level programming" that Typescript's terse syntax doesn't always let you express things in a way that's suitable for maintainability. You see the same kind of shortcomings with complex regular expressions. It starts to feel like you would almost want an extended syntax to deal with that sort of thing. Some languages actually do have alternative ways to build regexes that are more verbose but easier to read. Maybe MS will do something similar for Typescript in the future.

  • @andrew_ray
    @andrew_ray 3 месяца назад

    I don't really like taking the keyof a mapped type. I think it's nicer to take a helper instead:
    type PathInto =
    K extends string ? T[K] extends string ? K : `${K}.${PathInto}` : never;
    Or without the helper:
    type PathInto =
    keyof T extends infer K ? K extends string ? T[K] extends string ? K : `${K}.${PathInto}` : never : never;

  • @enjay86
    @enjay86 Год назад

    au, my brain!

  • @alextrofimov8320
    @alextrofimov8320 Год назад

    Why do you need to do ": keyof. { [K in EXP]: any } "instead of ": EXP"?

  • @shadowsir
    @shadowsir Год назад

    Whoa, didn't know this was possible, great job explaining it! :D

  • @shaikhdanish2793
    @shaikhdanish2793 Год назад

    Can we also program the object?

  • @karlockert
    @karlockert Год назад

    Could perhaps ‘infer K’ be used instead of ‘& string’ to Get the types correctly and also provide some fallback?

  • @coffeeandbytes9854
    @coffeeandbytes9854 Год назад

    This is my issue with TypeScript tutorials, there are only two skill levels being taught: Absolute Beginner or Dark Wizard.

  • @matttamal8332
    @matttamal8332 Год назад +1

    This is fantastic stuff Simon. I usually have to bash my head against problems like this when I make generic utilities that are used across the code base. One the the most fun times I've had in programming was creating destructive interference to collapse an type-generated object to filter out other types.
    Out of curiosity, do you have any good sources for type leveling programming that I can reference or learn this kind of stuff from on a more structured and fundamental level?

    • @sstur
      @sstur  Год назад

      Thanks Matt! I've heard good thing about type-level-typescript.com (but haven't been through it myself), check that out and let me know if it's awesome.

    • @matttamal8332
      @matttamal8332 Год назад +1

      @@sstur Awesome! I'll take a look into this! :)

  • @Nick-tv5pu
    @Nick-tv5pu Год назад +1

    Good heavens

  • @repe0
    @repe0 Год назад +1

    Many i18n libraries let you do this automatically. It will understand that there might be depth in your translation.json

    • @shadowsir
      @shadowsir Год назад

      Except this will throw a compile time error if the translation key doesn't exist in stead of at runtime.

    • @senor_m6673
      @senor_m6673 Год назад

      ​@@shadowsir i think thats what he meant, that for example i18next is already using this kind of functionality

  • @MaximeTrichard
    @MaximeTrichard Год назад

    How would you also allow "hello.greetings" for instance ? (stop the type mid-path)
    It can sometimes be useful, although this is not the case in your example obviously.
    Anyways, great explanation, thank you for that !

    • @seftikara651
      @seftikara651 Год назад

      you could change the
      ```
      T[K] extends Record ? `${K & string}.${Keywords & string}`
      ```
      part to
      ```
      T[K] extends Record ? `${K & string}.${Keywords & string}` | K
      ```
      the ```| K``` here inserts the bare(?) type along with the nested type. (unification)
      the complete one would look like this
      ```
      type Keywords = keyof {
      [K in keyof T as T[K] extends string ? K : T[K] extends Record ? `${K & string}.${Keywords & string}` | K : never]: any;
      };
      ```
      **I changed PathInto to Keywords btw, fits better for me

  • @LewisCowles
    @LewisCowles Год назад

    Do you have any stats on what these "fancy types" do to build times?

  • @joppekoers3992
    @joppekoers3992 Год назад +1

    This was great!
    But please share the code so I dont have to image-to-text it.

    • @sstur
      @sstur  Год назад

      The code from this video can be found here: bit.ly/iuebsku

  • @ofweb
    @ofweb Год назад

    That is a very nice explanation. Would it be possible to make the t function more strict on the return type? so that instead of just returning a string it returns a string litteral? so that when you add the key the complier already knows what the sting is and shows that on hover.

    • @stevenvaught9429
      @stevenvaught9429 Год назад

      If you do that, you're effectively writing the same logic in both js-land and type-land.
      You may be able to reify the type-land version and have code that can use that reified definition, but it wouldn't be doable without some library/plugin

  • @coolemur976
    @coolemur976 5 месяцев назад

    All of that just to have some intellisence on (string): string 😁

  • @000TheMatheus000
    @000TheMatheus000 Год назад

    can you type the returntype of this function so it knows exactly wich string you are returning based on the key passed?

    • @sstur
      @sstur  Год назад +1

      Yes, I believe this would be possible. But I don't know what the advantage to that would be. What do you have in mind?

    • @dolevgo8535
      @dolevgo8535 Год назад

      @@sstur A good use for it would be to know if there are mappings inside the return type(such as {user}), that way the developer would know to pass those parameters- or even enforce it in the function signature!

    • @sstur
      @sstur  Год назад

      @@dolevgo8535 Ah, got it. Coincidentally I have a vid coming up that should help with this. I also have some examples that might help. Will post back here.

  • @dawid_dahl
    @dawid_dahl 8 месяцев назад

    This is starting to look like Haskell code, haha.

  • @andrepadez
    @andrepadez Год назад

    Thank you for this video, i was looking for something short and concise to show clients "why you shouldn't use TS"

  • @disel920
    @disel920 Год назад

    Cool, my version without `& string`
    const data = {
    env: {
    name: "",
    payload: {
    price: 0,
    size: 0,
    },
    },
    };
    type DataType = typeof data;
    type PathsInRecord = keyof {
    [KEY in keyof OBJ as OBJ[KEY] extends object
    ? KEY extends string | number
    ? PathsInRecord extends string
    ? KEY | `${KEY}.${PathsInRecord}`
    : never
    : never
    : KEY]: never;
    };
    type res = PathsInRecord;
    const a: res = "env";
    const a0: res = "env.name";
    const a1: res = "env.payload";
    const a2: res = "env.payload.price";

  • @avneet12284
    @avneet12284 Год назад

    No repository link? Not good.
    But excellent video

  • @dmitriynesterkin5672
    @dmitriynesterkin5672 Год назад +1

    This is a great example for why not to use Typescript. What exactly does the baby-sitting code accomplish? Imagine the amounts of time wasted writing, reading, and maintaining shit like this. Write plain JS and use tests to assert code quality.

    • @nicholasdenaro347
      @nicholasdenaro347 Год назад

      If it's a great example, please explain why this is bad. To me this looks fine. While it is moderately complex code, it does ensure "type" safety. Which part of it will need maintenance?

    • @dmitriynesterkin5672
      @dmitriynesterkin5672 Год назад

      ​@@nicholasdenaro347 What exactly is the benefit of the code below? It took him more time to type that than the actual function. The snippet would work only for hard-coded objects and then a "special" t() function has to be used to make sure that correct properties are passed to get(). More code. It's better to just use get() directly and if a path is misspelled, then undefined will be returned, and if it is used, then an error will result and will have to be corrected. All of this is less time-expensive than creating hand-holding type overhead.
      type PathInto = keyof {
      [K in keyof T as T[K] extends string
      ? K
      : T[K] extends Record
      ? `${K & string}.${PathInto & string}`
      : never]: any;

  • @floofyjr
    @floofyjr 4 месяца назад

    The worst tutorial i've ever seen

  • @markemerson98
    @markemerson98 Год назад +1

    another reason i detest typescript...

  • @BobbyBundlez
    @BobbyBundlez Год назад

    so lame lol. TS at this level takes the joy and fun out of literally everything

  • @pawegraczyk6050
    @pawegraczyk6050 Год назад +2

    Useless

  • @solevow
    @solevow Месяц назад

    my brain explode when it comes to how to dynamic extract key or value from a nested object, espcially when you use [K in keyof T as (...)], really weired syntax, hard to understand why this works , and I found this less code also works, but not quite sure why it works
    type PathTo = keyof {
    [K in keyof T as T[K] extends Record
    ? `${K & string}.${PathTo}`
    : K]: unknown;
    };

  • @jeetchheda8916
    @jeetchheda8916 2 дня назад

    i assume/hope things must have changed as of September 2024 🥲🥲

  • @RexGalilae
    @RexGalilae 2 дня назад

    I think TS, at a conceptual level, must've been built on top of works in set theory published by pure mathematicians
    This thing seems to come straight out of a college level math textbook lmao

  • @nazaka9904
    @nazaka9904 Год назад

    i see it right on the next day after doing exactly the same :)

  • @SuperRoli123
    @SuperRoli123 Год назад +2

    This type programming is awesome

  • @andrepadez
    @andrepadez Год назад +1

    typescript is insane, and not in a good way

    • @BobbyBundlez
      @BobbyBundlez Год назад

      i know. its still absolutely ridiculous to me. horrific level of yet more abstraction to fix a basic problem JS had from the beginning. being loosely typed lol

  • @Vali615
    @Vali615 Год назад

    Would be much easier to use a recursive function to generate those strings. If you add an extra level, you need to change all your code...

  • @ARKGAMING
    @ARKGAMING Год назад

    This was a very interesting video. I really enjoyed it

  • @danieljulien4099
    @danieljulien4099 Год назад

    so great and horrific at the same time lol. my 1st encounter with type programming going so far. 🤯😭🔥

    • @sstur
      @sstur  Год назад +2

      Haha, yes it's a bit horrific at first sight! and the rabbit hole goes deep. But TS is great for a lot of things, even if you don't go deep into type level programming!

    • @danieljulien4099
      @danieljulien4099 Год назад +2

      @@sstur yes! i learned A LOT watching your video + you explain very well, so thank you for that!