Loading video player...
absolutely blew my mind. Now, it doesn't
actually look like that solved our
problem. Actually, it looks like it's
not quite working as we expect.
I have always struggled with advanced
TypeScript features. So, I decided to
test my limits by going through various
TypeScript challenges to see if I'm able
to solve them, all while actually
explaining everything that I'm doing
step by step so that we can become
better Typescript developers together.
But what I didn't expect from this is
that I was actually able to use a super
niche TypeScript feature to solve one of
these challenges in a way that the
creator did not expect.
Welcome back to WebDev Simplified. My
name is Kyle and my job is to simplify
the web for you so you can start
building your dream project sooner. And
I have three challenges I'm going to be
tackling. The first is rated as easy,
the second one is medium, and the third
one is hard. And I'm honestly quite
scared about this one because even the
medium challenges I've looked at in the
past are very, very difficult. Now, to
understand how all this works, we're
going to just use their sample data
right here, which is just a warm-up.
Essentially, at the very top, we have a
question, and all we need to do is we
need to solve whatever this question is.
It's going to ask us to create a type
that does something. In this case, we're
just creating a type of string. That's
really straightforward. At the bottom,
you'll see here, this is the type we're
going to create, whatever it is right
here in this your code here section. And
then we have some test cases that test
to make sure that this is the correct
type. So if we just type this as a
string, you'll notice down here all of
our test cases remove the errors, which
means that they are currently passing.
So let's go ahead and actually look at
the very first example, which is this
exclude one. Essentially, all we're
doing in this example is we're
reimplementing the built-in exclude type
that's inside of TypeScript in our own
way to figure out how it works. And this
should be rated as easy, even though
it's going to use some more advanced
TypeScript features. Now, you will
notice at the top there are some tints.
For example, it's recommending like a
union and built-in. These are like tags
or categories, which sometimes can help
you steer you in the correct direction.
So if we look down here, you can see our
different test cases. So we're excluding
from A B C the value of A. So we're left
over with just B and C. And here we have
ABC. We're excluding A and B. And we
should be left with just C. Now
obviously I could just use the built-in
exclude type from TypeScript. And if I
do that, it's going to completely solve
my problem. But of course, that kind of
defeats the entire purpose. So let's go
ahead and actually look at how we would
create this exclude type. Essentially,
we have this type T that we know has a
bunch of different things. And we have
another type called U that we know is a
subset of our type T. So we just want to
figure out what is in U and remove that
specifically from T. Now in Typescript
whenever you want to do like an if
statement that's always going to be done
using an extends keyword. So we can say
T extends right here. And this allows us
to actually dive into what is T. So for
example, we should say T extend string.
And now if T is a string, whatever is
after our question mark is going to
return. So for example, true. And if T
is not a string, then this false section
is going to be what's returned. And this
works great when you have a single value
for t. But when you have for example a
union of values or maybe an object of
values, then this extends keyword works
a little bit differently and instead it
applies to every single value inside of
there individually. So it's checking
every single value of essentially t and
checking if those are all strings each
one individually. So because of that we
can almost think of this as a for loop
looping over each value inside of t and
then doing some type of if comparison on
each one of those values. It's then
going to return true or false for each
of those individual values. And now
we're going to get a return value that
is essentially a bunch of different trus
and false combined together. We can
actually use this relatively easily to
just put u here instead of string. So
now we're looping through each value in
t and seeing if it extends u. So
essentially the very first time I loop
through here I have a if I'm looking at
this very first use case and I'm saying
does a extend a. Well in this case
extends essentially acts like an equal
sign. So we are saying yes that is true.
So it's going to return the value of
true. The second time it's going to loop
through on my value V. And you see B
extends A, not true. So it'll return
false. And finally here, it'll check C
again. And C does not extend A. So it's
going to return false for that. What I
essentially want to do though is I want
to take those true false and instead I
want to convert it to a brand new union.
That is going to be just the values that
we're not excluding. So every time it
returns true, I want to remove that
value. So pretend it doesn't exist. That
is where the never keyword in Typescript
comes in. It essentially says this can
never happen. And so it pretends that
type doesn't even exist. And otherwise I
want to just return that value as is. So
I can actually just return T, which as
we know in this case is each individual
instance of that value T. This is what
makes dealing with these types so
confusing because T when we pass it in
is essentially this full union. But when
I use it in this extends, it's almost
acting like a loop. And while I'm inside
that loop, so inside this conditional
statement, this T value instead of
representing the full union only
represents each individual value. So
again, the first time we loop through,
we get a a is equal to a. So we return
never. Finally, B is not equal to A. So
we return B as is. Same thing with C. C
is not equal to A. So we return C as is.
So the entire purpose of this my
exclude, as you can see, all the test
cases are passing. Is to really figure
out that this extends keyword does so
much more than just a simple if check.
It can also be used for essentially a
for loop that is going to map over those
values and return to you a brand new
value from them. And that is kind of the
core of almost every single advanced
TypeScript thing you write. And I'm sure
in the other cases we look at, we're
going to run into the exact same issues.
Now, speaking of, let's look at our very
next one here, which is going to be
kebab case. So, in this one, all we want
to do is replace the camel case or
Pascal case strings and convert them to
kebab case. So, here you can see we have
foo barb baz is converted to
fu-ashbar-bas.
Essentially, anytime that I have a
capital letter, I want to lowercase that
letter and put a dash in front of it to
give myself a kebab case instead of a
camel case. Now, if we look down here at
all the different examples we have, we
can see some of the edge cases in place.
So, for example, if we have a dash, it
should just return as is. Empty string
should not be changed. Anything that's
not a capital or lowercase letter should
stay the same. And here, for example, if
we have like an underscore that stays
the same. Literally, the only thing
we're looking at is every time we have a
capital letter, remove that capital,
make it a lowercase, and put a dash
right before that. That's essentially
what we want to do in this particular
challenge. So, when I think of this
challenge, I first need to be able to
recursively loop through this because I
kind of need to loop through each letter
of my Arab string here. And that's a
quite difficult thing to do inside of
TypeScript. There's no like for loops or
recursion that you could do naturally.
You have to use that fancy extends
keyword to figure this all out for you.
So we have s which we know is going to
be an extr. I can even just come in here
and say it extends string because it
doesn't really make sense if it doesn't
extend a string. And now what I can do
with this s is I can say s extend and I
want to try to essentially get each
individual character of my value. So I
want to get okay this f if this f is a
capital letter I want to turn this to
lowercase and then I want to get o and o
and b I want to loop through each
individual string as is. Well, this is
something you can do with the infer
keyword inside of TypeScript. So, what I
can do in here is get a template string
literal. This is actually kind of tinted
to us up here with the template literal
category that this is part of. So, here
I have my template string and inside
here I can actually put essentially
variable values. In JavaScript, you
could put like a variable for example
like my name and that would put my name
inside of this string. But in
Typescript, what we could do is we can
use the infer keyword and then give this
a variable value. For example, I can
infer a and now this a variable is just
equal to whatever is inside my string.
Now, in this case, it doesn't really
make much sense to do this because I
just have a string as is. But if I were
to come in here and do like a followed
by two O's, this A is going to be
whatever is followed by two O's. So, in
our case, it would be a capital F here
because that's followed by two O's. And
that's what A would equal in this
particular string. Now, in my case, I
want to loop through each individual
letter one by one. So, we can actually
stack two infers next to each other by
doing this. We have infer A and we have
infer B. Now, you may be thinking, okay,
what happens in this scenario? because
now we have two dynamic variables that
we need to create. Essentially what
TypeScript does is it tries to figure
out what this first variable should be
and it tries to make it as small as
humanly possible. So in our case we just
try to infer one single character of our
string. So F in our case and then the
final infer value in our list is just
whatever is left over. So this B value
is literally our entire string and A is
just the very first character of our
string. So in our very first example of
fuar baz, essentially we have a is equal
to just the capital f and then b is
going to be equal to everything except
for that f. So essentially this first
value is going to be the minimal amount
of content and the second value is just
all the rest of our string. And we can
even just call it rest to make it a
little bit more self-explanatory. Now
what I essentially want to do from here
is I want to take that letter a because
now I have an individual letter I can
work with and I want to determine is
this uppercase or lowerase. If it's
lower case just leave it as is. But if
it's uppercase, I want to change it to a
lowercase and put a dash in front of it.
So let's go ahead and try to actually do
that. So in our first question mark
section, this is essentially saying,
okay, our string extends this section,
which means our string essentially is
long enough to have two different parts
to it. So this is where our main code is
going to go. And then after our colon
here, we're just going to put s as the
value we're returning because that
essentially means that our string does
not match this particular format, which
means our string is too short to get
multiple different values from it.
Essentially, we have an empty string.
And once we get to an empty string, I
just want to return my string as is
because in this middle here, we're going
to do recursive code. And it'll make
more sense why we're returning this
string right here. So, let's come on
down here. And I want to first of all,
like I said, check to see is a uppercase
or lowerase. And to do an if check, we
need to use extends. So, we can say a
extends. And I want to check to see if
it's uppercase. So, I think I can just
use the uppercase type here. And I can
paste in a just like that. So, now I'm
checking is a an uppercase value. If so,
do something. otherwise do something
else. So what we can do is for now I can
just put never here and never here to
get my type stuff correct. And now for
this first case if we have an uppercase
letter for a I want to make it lowercase
and I want to put a dash in front of it.
Otherwise if it's not uppercase I just
want to return my value as is without
doing anything complicated. So here I
can just recursively call kebab case. I
know that my a character is fine. So I'm
just going to put my a character at the
start of the string. Then I'm going to
call kebab case and in it I'm just going
to pass all the rest of the values that
we didn't use. So essentially what I've
done here is now I've taken this a
character. I know that it's already
lowercase so I can leave it as is and
now I'm recursively calling this
function by trying to get the next set
of my string. So after I do this the
first time f is the value for a and you
know I have all my other stuff coming
afterwards. The next time I call this
function everything but the first
character is going to be used because
we're recursively calling it. So then I
can come inside of here in the case
where we have essentially an uppercase
version of a. Well, I need to make this
lowerase. So we can use lowercase to
make that a lowercase value and we can
put a dash in front of it because we
need a dash in front because that's how
you kebab case different values. So now
just by doing that if we give it a quick
save it actually looks like some of our
test cases are passing which is great.
So let's go ahead and actually see why
some of these aren't passing. That
should be for example this third test
right here. I really think that this one
should be passing because all we're
using is fu-ashbar. There's not even any
uppercase letters at all. So, it should
just be essentially returning our string
as is. Now, an easy way to type this
inside of TypeScript is just to create a
brand new type. We'll call it test. And
we'll just set it equal to essentially
calling that kebab case. And this will
tell us what we get back. And if we
hover over this, you'll notice it's
actually giving us two dashes here,
which is kind of interesting. So,
essentially, whenever we encounter a
dash, it thinks that's an uppercase
letter. I'm guessing that's what's
happening. So, uppercase actually is
returning true for things like dashes.
And it looks like even emojis are
determined to be uppercase. So I think
if we just swap this to be lowercase
because if we get these false positives
where like a dash is uppercase or
uppercase and like a emoji is uppercase.
I'm assuming we'll get the exact same
false positives for lowercase and we can
just swap our cases around. So here we
just swap the order of these different
values. And actually it does look like
that solved it. If we look at our test
here, you can see it is correct because
now even things that aren't lowercase
values, for example, they're just like
dashes or underscores, those will be
treated as if they were a lowercase
value and passed along normally. So now
that solves all of these different
values. We only have these three that
aren't working as we expect. So let's go
ahead and we'll just copy over the text
to see why these ones aren't working as
we expect. And if we look at our test
here, we can see, okay, it looks like
the reason why is because the very first
capital letter is getting a dash put in
front of it when in reality it
definitely should not be because it's
the first character and the first
character should just be lowercased with
no dash added in front of it at all.
Now, I think there are two different
ways we can solve this. There's one way
I can definitely think of off the top of
my head that's easy, but in my opinion
is a little bit hacky, and that is that
we can use essentially an additional
parameter inside of our kebab case
function that is optional. So, you don't
normally pass it in, but allows us to
keep track of things. Often, it's called
like an accumulator. So, like in a
reduce function, you use an accumulator
to keep track of things. It's the exact
same thing in this particular case. So,
we can use an accumulator of some form
that just says like is first and we can
set that to be true by default. We can
say that it extends a boolean. So it's
either going to be true or false. By
default, it is going to be set to true.
But every single time that we call this,
we can just set this to false. So that
now it is not the first value. So then
essentially what happens is we can add
another if check inside of here by just
saying after that colon, we can say is
first extends true. If it's the very
first one, then we essentially want to
just lowercase the letter and not push
the dash in front of it. Otherwise, we
want to put the dash in front of it. I
think this alone will actually solve our
problem. So here we just remove that
dash. And now actually it does look like
we've solved all of those individual
cases. Now I'm personally not a fan of
this approach because if I don't need to
add these accumulator values on there, I
don't like to because now all of a
sudden I can come in here and just pass
false and all of a sudden I break my
entire thing even though that is a valid
way for me to be able to pass that in.
Now it is saying that type false does
not satisfy the constraint true. That's
because this thing is not passing. And
if I just paste this up here, you can
see I'm getting that dash back in the
front because I'm overwriting
essentially true false if this is the
first time I'm accessing this or not. So
I don't really like this particular way
of doing things. And it also kind of
goes against the spirit because they
didn't have this as part of the
challenge. So I want to try to solve
this in a different approach. So let's
go ahead and get rid of all of that is
first stuff that we added. We'll get rid
of this one and we'll get rid of those
extra false checks in there. So now we
should be left just with those original
failures that we had. And I'm just going
to again create that type test.
There we go. So we at least have this
thing that we can look at. Now I think
the way that we're going to be able to
solve this problem is by essentially
assuming that the first character we
don't really care about and instead
we're going to be focusing on all of the
later characters in our actual check
here. So what we can do with our first
character is just lowercase it no matter
what because we always know that it
needs to get lowercased. And then we can
work on the next set of characters
inside of our list after that. So, when
we're doing our check to see if we have
lowercase or uppercase values, I
actually want to use the rest of the
characters in our string. And
unfortunately, lowercase checks to see
if an entire string is lowercase. And
uppercase checks to see if an entire
string is uppercase. But we actually
have another one called capitalize. And
this checks to see essentially if the
first character of each word is a
uppercase letter. So, we can come in
here with rest. And if our rest
character extends the capitalized
version, that means that they both start
with a capital letter. And now we know
we have a capital letter at the start of
our string, we can take that capital
letter and we can essentially make it
lowercase and put a dash in front of it.
So let's go ahead and we know that we
want to essentially flop the order of
these because we're now essentially
doing our check in the reverse order.
And in this case where we have a
capitalize, I want to put a dash in
front of essentially the rest of my
character. So the dash is going to go
right here. I always lowercase my first
letter no matter what because it doesn't
matter if it's uppercase or lowercase.
The first letter should always be a
lowercase value and we don't have to put
any dashes in front of it. And then here
with the rest of our values, I want to
lowercase just the first letter. So I
think we have not capitalized or
something along those lines. That's like
the opposite of capitalized. Let's come
in here and actually see what it's
called.
Or maybe it's uncalized. There we go.
Unc capitalized. Essentially, this is
the exact opposite of capitaliz. it
converts the first character of the
string to lowercase. That's exactly what
we want to do. So now in this particular
case, we're lowercasing our first
letter. And if we have a capitalized
first letter in the rest of our string,
we're putting a dash in front of it. And
then we're lowercasing that one letter
and just calling the rest of our code as
is. Next, essentially, I want to do the
same thing in this section down below.
But I don't need to worry about
uncalizing any letters or putting a
dash. So I can just get rid of this
uncalized content and get rid of that
dash. Now let's go ahead and actually
see what the code is being returned
because obviously we have tons of errors
down here and right now it's returning
fu bar baz which it has a dash at the
end though which is definitely not what
we want. I think the reason that this is
happening is because eventually we get
to the point where rest is an empty
string because our string gets down to
one single character. So a is that one
character and the rest of our string is
an empty string. So we have a little bit
of a problem there. Uh there's a few
different ways that I think we could
solve this, but the easiest I think is
just by swapping this with uncalize here
and then swapping the order of these
because then essentially an empty string
is going to be considered essentially
uncalized. So then we can make sure that
we have all of this code as is. And
yeah, I think if we just save actually
that will solve our particular problem.
So this makes it so that empty strings
follow this test case instead of
following down here. I could also add in
an additional extends where I just check
to see if it's equal to an empty string
as well. But this is a little bit easier
because doing like an and check or an or
check in Typescript requires you to do
lots of turninary nesting which is
obviously not ideal. Now technically
this solves the problem that we're
looking for. But I actually want to take
this a step further by using one of the
techniques that absolutely blew my mind
the first time I heard about it. And
that is the idea of tail recursion. If
we take a look at our code, everything
looks fine. But what happens if we try
to execute this on a string that's very,
very long. Let's just come in here and
paste this down a few times. And you'll
notice our code goes from working to
getting an error. And if we hover over
this, it essentially says type
instantiation is excessively deep and
possibly infinite. Essentially, if you
recursively call a type too many times,
usually it's like 40 or 50 times,
TypeScript essentially says, I have no
idea what to do. This is too deep, too
much recursion. I'm just going to throw
an error because I don't know how to
handle this excessively recursive thing.
So instead, what we need to do is we
need to figure out a way to actually
handle this recursiveness. And that is
by using something called tail recursion
which like I said blew my mind the very
first time I heard about it because it
essentially allows you to solve these
particular problems relatively easily.
And all tail recursion is it's a fancy
word and there's a lot of fancy
definitions but from my simple
understanding essentially what we need
to do is call the recursive function
before we do any additional checks
inside of our code and just immediately
return that value. So in our case here
we have our simple you know just extends
check. That's perfectly fine but then
we're doing another check down here.
This is like our first main check. So
we're doing a check to see if things are
capitalized and then we're extending
code in front of our kebab case. So
we're returning an additional value
that's not just our, you know, recursive
version of this. So we have a bunch of
stuff going on, additional checks, as
well as additional data being added onto
our kebab case, which means that it's no
longer tail recursive because we're not
just returning the value of this thing.
We're instead returning the value plus
something added onto the start of it
every single time. So instead, we need
to make it so that whenever we call
kebab case, it's going to just return
itself with no other stuff being added
on. The easiest way for us to do that is
to actually add one additional level of
nesting at the very very top where we
just call kebab case just like this with
the rest of our code. And what we need
to do is just say that infers some
random type. Doesn't matter what it is.
We're just going to call this our
string. So we'll just say str just like
that. Then we can put our question mark
syntax. And we can come down here with
our colon syntax as well. And for this
one, we're just going to put never as
the type because it should never be
possible for this not to infer this
string type right here. Now, I also need
to make sure I come in here with an
extend just like that. So, essentially,
we're saying, hey, this kebab case
extends inferring whatever it is. So,
essentially, it extends itself is what
we're saying. We're just taking whatever
the value of this is and putting in a
new variable called str. So now, every
time we're using kebab case, we can
replace that with str. And we can come
in here,
make sure I get that correct, and we can
replace the exact same thing down here
where we have this str. And that should
actually solve our particular problems.
I am noticing that we're getting a
little bit of an error because this
could be a string, number, boolean, blah
blah blah, all these different things.
So, we of course need to add another
check that just says str extends string,
which in our case, we know that it
should extend a string, but TypeScript's
not smart enough to do that. So, if it
doesn't extend a string, we'll return
never down here. Now, to make sure that
this works, I essentially also need to
copy over this fooarb baz a bunch of
times to give us essentially the same
number. And I copied it down a few too
many times. There we go. And now you can
see that this string that was too long
originally is now able to be solved
because of this tail recursion. We are
making sure that every time we call
kebab case, when it's being called
recursively, it just returns immediately
with no extra stuff being added on. You
can see we call kebab case with whatever
the rest of this is. No extra stuff is
being added on. No other checks are
being made. it just returns itself. So,
it's able to essentially exit out. But
since we still need to do additional
things with this content, we just add a
simple extends infer right here that
essentially takes whatever this value
was supposed to be, puts it in a brand
new variable, and now we don't have to
worry about that recursive stuff because
TypeScript is smart enough to just
essentially evaluate this section for
the recursive portion, and figure out
everything else on its own. Now, this
wasn't actually part of the example
itself, but I read about this when I was
doing some TypeScript research for
different projects, and it blew my mind,
so I had to mention it to you because
it's absolutely amazing. Now, we get to
move on to what is hopefully probably
going to be by far the hardest thing
because it's in the hard category. And
that is implementing a git function. And
this is something that's really common
in a lot of different libraries such as
load dash. And it's a convenient way to
be able to access nested data. So for
example here if we have this type that
is data which has foo bar it's got a
value it's got count all these
additional things inside of it. We just
want to call get data and we can say
like fu.bar.count and it's going to go
into fu go into bar and give us the
actual value for count or we can say you
know we want to get the data for just
hello and it's going to check this hello
string. So it allows us to essentially
recursively and nested go through an
object and get all the different values
from it. Importantly it says accessing
arrays is not required in this
challenge. So we don't have to worry
about that which is kind of nice. we can
instead just focus on objects. So if we
take a look at our test cases, I think
this will help inform what we actually
want to do. We can see obviously if
something doesn't exist, we return
never. That makes a lot of sense. A
single key just returns whatever that
value is. Hello goes to world.
fu.bar.count, as you can see, just goes
through the object itself. This one,
fu.bar, is actually the key itself. So
we need to make sure we not only use
dots for nesting further into our
object, but also a key could have a dot
inside of it. And we probably need to
check that first before we do our
nesting sections. So I think for the
most part this isn't going to be too
complicated for us to do. Yeah, you can
see foo bar is returning the full object
because foo bar gives us this whole
object. So let's try to go ahead and
think about what we want to do. T is
going to be our data object. So I'm
going to call this object just to give
it a better name. And K, that's the key.
We can just call it key. It's perfectly
fine. Now if we didn't have to worry
about nesting, I could just come in here
and I could say object of key. And
that's going to get us whatever the
object at that key is. And obviously
that solves these two particular
problems right here. Now we are getting
a little bit of type error because key
cannot be used to index something that's
a type of object. So we know that key is
going to be a string. So we can say
extend string just like that. And we
know that this is going to be an object.
So we could say extend record of
property key
and it's going to be unknown because we
don't know what data type that
particular thing is. So I think we're
already on to kind of a good start.
Essentially we want to get the value
from the object and if we have a value
from that we just can return it as is.
So we can say if this extends any then
that means that we have an value at that
particular key. So we can just return
that object key
just like that. Otherwise we need to do
something with that particular value.
Now in my case I'm just going to return
never to make sure our other test cases
are still passing. And as you can see
they are because we have something at
that key. So it's returning what is at
that key. Now this one right here should
be returning never. And I don't quite
know why it's not. So let's go ahead and
take a look at it. We're going to
convert this into a type just like that.
We'll say t is equal to that. And we'll
hover over what t is. And you can see
the type here is unknown. And already I
think I know the reason for this. And
that's because right now when we do
object of key, it's essentially getting
us this generic unknown type. It doesn't
know what it is. Every single time it's
just returning our unknown type for us
because it can't figure that out. So
instead, we need to use some infer
keywords here to actually make this work
like we expect it to. So I can say
object key extends infer and we'll just
say prop because that's going to be our
property value itself. So now we're just
essentially taking whatever this is and
putting in its own type called prop.
Then what we can do is we can say prop
extends any and then we're going to just
return that value. Otherwise we return
never. And again we'll return never down
here. If for some reason this does not
extend properly. Now it doesn't actually
look like that solved our problem and we
may not even need this prop. So, I'm
pretty much going to get rid of all of
this code and instead of extending any,
I'm going to extend unknown because it
seems like what's happening is when it
can't find the key, it's returning
unknown instead of returning never,
which is a little bit unintuitive for my
own thought processes, but it kind of
makes sense because it could have a key
at that value. It just doesn't know it.
So, we can come in here and if it
extends unknown, then we return never.
Otherwise, what we're going to do is
we're going to return our object of key
just like that. And if we go ahead and
we take a look at this, you can see this
particular one is returning never, which
is like we expect. But for some reason,
our hello one is not working anymore. So
we can say type h is equal to our hello
one. And this one is also returning
never. So it's essentially always
returning never because everything
extends unknown. I believe I think
that's the thought process behind it. So
I think instead of what we need to do
here is just check if it's a key of this
particular object. That's probably going
to be a much easier way to do this. So
we can say if our key extends key of
object then that's going to be
essentially the same thing. So we can
swap the order that we were doing these
in. Put this one as never just like
that. And that actually did solve our
particular problem. You can see this one
is now never and this one is set to the
correct type. So I was just trying to be
a little bit fancy there with this any
unknown stuff. We just really need to
check is this a key that exists on the
object. If it is a key that exists on
the object then obviously what we need
to do is just return it as is.
Otherwise, instead of returning never,
we actually need to do some more nesting
because it could be the case where it's
something that doesn't exist or it could
be the case of where we just need to get
the first character and essentially
rerun it back through this particular
function for getting. So now what I can
do here is the same thing I did before
to be able to get the character before
the dot and all the characters after the
dot. So I can say that my key extends
infer and I specifically want to infer
here the start of it. So we'll call this
our key one. So we'll just say K1 and
we're going to put a dot
and we'll come in here with K2. So this
is everything that's after. We'll call
this rest because it's a little bit
easier. And then this is going to be our
internal key that we want to use. Now we
need to come in here. We'll put never
for both of these just so we can get
some type safety stuff going on. I need
to make sure my infer keyword is inside
here. Whoops. There we go. Now we give
that a quick save. And that looks great.
So now if our key extends this
particular thing, we can now essentially
check to see if our object key is
essentially a key. So now we can say
here essentially the exact same thing we
did up here K1 extends
key of object. If that's the case then
what we want to do is we want to call
get
and we want to pass it in essentially
all of the remaining keys and that's
going to be for our key section and our
object is just going to be object
of K1 and we want to return the rest of
our keys afterwards. There we go. Now in
the else case then I think we can safely
return never because if this K1 is not a
key of the object then it's essentially
going to be never. So we can just come
in here and we can put never at the end
there. And now we're getting a little
bit of an error here. It says object K1
does not satisfy the constraint record
property key unknown. That technically
makes sense because it doesn't know what
this particular type is. I think we can
just get rid of this entire extend
section up here and it's going to be
fine. And actually it looks like that
entirely solved the problem that we're
going with. I thought we'd have to go a
little bit more complicated, but it
makes sense that this is solving it like
we would expect it to. Now, I think
we're going to run into the same error
where we can't do our like 50 levels of
nesting. But the nice thing about this
is the 50 levels nesting is for each
dot. So, we would have to have an object
with over 50 different levels of nesting
that we go through. But, we can kind of
look at this to see if there's maybe a
different way that we can go about
solving this particular problem. And I
think we can actually simplify this
slightly because essentially we don't
really care if K1 extends the key of the
object at all. if it doesn't extend it,
it's going to return never. But if we
just try to access object K1, it's going
to return never if that thing doesn't
actually exist. So instead, what we can
do is I think we can just put our
recursive call directly right here and
get rid of this entire nested section.
If we give that a save, actually, it
looks like it's not quite working as we
expect because yeah, K1 can't be used to
index this because we don't know that
it's a key of it. So that makes sense.
We can bring it back to what we had
before because this does work like we
expected to. Now, I am curious to see if
this will work with arrays though. So
let's come in here and actually like add
in an array. We'll just say r is going
to be an array that has a value of 1 2 3
just like that. And let's come and just
do a simple test. We'll say is it foo r
and that's going to be of zero. So I
think we just need to do zero and that
should be equal to one just like that.
And actually it does work with arrays as
well. If we come in here with two that
should be set equal to two and actually
sorry one index one should be set to
two. So yes this does work with arrays
which is really cool. I think we could
even do nested objects. For example,
inside here I could say bar is actually
equal to one now. And what I could do is
I could say array of0.bar
that should be equal to one. And that
test case does pass as well. And that's
essentially because this key of property
essentially arrays are objects inside of
JavaScript. So it's just able to
determine okay the key here is going to
be two for example or or sorry not two
it's going to be an index of one or zero
in our particular case. So essentially,
it's just working like that index
accessing of arrays works exactly the
same as accessing the actual properties
on objects. So it's nice that this
actually works for arrays, even though
it's not part of the challenge. Now, if
you enjoyed this video, you're going to
love these other two videos where I
create a full React router from scratch
with full type safety and I implement
internationalizations with massive type
safety unlike any library I've ever seen
before. Both those videos, they're going
to be linked right over here for you.
With that said, thank you very much for
watching and have a good
FREE TypeScript Utility Types Cheat Sheet: https://webdevsimplified.com/ts-utility-types-cheat-sheet.html Using TypeScript is relatively easy, but writing complex TypeScript types is incredibly difficult. In this video I will be challenging myself with 3 different levels of TypeScript challenge (easy, medium, and hard) to see if I can conquer these ever increasingly difficult TypeScript problems. 📚 Materials/References: FREE TypeScript Utility Types Cheat Sheet: https://webdevsimplified.com/ts-utility-types-cheat-sheet.html 🌎 Find Me Here: My Blog: https://blog.webdevsimplified.com My Courses: https://courses.webdevsimplified.com Patreon: https://www.patreon.com/WebDevSimplified Twitter: https://twitter.com/DevSimplified Discord: https://discord.gg/7StTjnR GitHub: https://github.com/WebDevSimplified CodePen: https://codepen.io/WebDevSimplified ⏱️ Timestamps: 00:00 - Introduction 00:50 - How the challenges work 01:22 - Challenge #1 05:04 - Challenge #2 20:51 - Challenge #3 #TypeScript #WDS #TSChallenges