Trying to index an array type with .get(...path)
The code below prints "string | undefined" which at first kind of makes sense but after thinking about it, I feel like it should just be "string". While regular array indexes can return undefined, types are never actually turned into real values so an index can never actually fail. It also doesn't match TypeScript types where indexing an array type like
string[][0]
equals string
.
My use case is that I'm building an automatic form generator where I am using .get(...path) to get a specific part of the schema in a deeply nested object schema. I want to be able to effectively get the array's type but to my knowledge it's impossible to do so because any array index always adds | undefined
so I cannot differentiate between string[]
and (string | undefined)[]
for example.24 Replies
Well first of all, it's fun to see other people thinking about these kinds of questions in detail because they're really interesting! There are some cases where ArkType specifically diverges from TypeScript to be safer or more precise and this is one of them
TypeScript's handling of index access is quite inconsistent. There is a
noUncheckedIndexAccess
setting that adds | undefined
to your value when you access it in JS, but has no effect on how string[][number]
is evaluated.
The result of a property access is just the answer to the question "what possible value could I have here"?
If I have the type { foo?: number }
, it wouldn't make sense to say that o.foo
couldn't include undefined
. What you're asking for is more like extracting part of the structure of the type itself.
Luckily I just released an API allowing these kinds of structural queries on the internal type system from @ark/schema
! It may require some digging through the structure of the type system to figure out how to get exactly what you want, but it's the coolest part of the project anyways:
Out of interest, what's the output with
string[] > 1
? Because technically there it is guaranteed to be string. Although I can see it not being worth the cost to implement the logicHahah, yeah I literally tested that exact case as I was writing the example it includes
undefined
still but you're right it shouldn't there.
My downfall was probably making sure I reduced [string, ...string[]]
and [...string[], string]
to the same type (string[] > 1
) but then you lose the tuple structure so I'd have to add some logic to put it back
It's actually probably pretty easy if you want to give it a shot there's tons of properties on a sequence node like minRequiredLength
I actually might take a look this weekend if it's easy enough.
Definitely way easier than the last stuff you were doing 😅 I will get to that PR tonight btw sorry I've been a bit overwhelmed trying to juggle consulting work
Sounds good :)
Thinking about it some more I guess the behaviour isn't black and white. I thought it was nice that you could pass through path as if it were using optional chaining:
But then you don't know where the
| undefined
comes from. Also TS errors when .get has more than 3 arguments, lol I guess I'm using the wrong tool for the job
I'm also using the select function, it seemed perfect for this and its the reason I chose arktype :) Although I didn't know it was powerful enough to traverse the whole structure which is why I was using it in conjunction with .get(). I'll take a look through the source!Yeah I specifically only built 3 overloads because there should almost never be a good reason to extract types that deeply.
What are you trying to build?
Yeah it's a lossy transformation, the only goal is to answer the question "what values could be allowed at this path?"
My plan was to make a vue library, kind of similar to vee-validate, for a personal project of mine.
API would be something like this:
Each
<FormControl>
would render label, input and description HTML elements that take from info from arktype schema. Since the path leads to a number type, it would automatically render as a slider with the min, max and default values taken from the arktype validation. My project is basically an editor for a decently large and nested JSON object so I thought this would be a nice way to avoid specifying things like min and max values multiple times throughout the project.Ahh okay I see. Yeah
.get
is definitely not the right tool for traversing an object
Another potentially less intimidating approach you can use for non-arrays is .props
The types will be very helpful and you don't have to learn the internal schema representation (although to do what I mentioned with an array you would )I was thinking .get was nice for it's type safe object path
Parameters<typeof schema.get>
🤔 not sure that's a good idea though
Hmm props does seem like another good option. I was also thinking about just converting it to a JSON schema and doing it that way but I guess props keeps more arktype specific information?Do you have the JSON structure ahead of time? I thougth you meant it was dynamic and you had to generate it
In which case type safety won't help
ArkType's JSON representation is richer than JSON schema you can look at that if you want using
.json
but that prop is not typed.
You are probably underestimating the complexity of traversing a structure that can include unions, morphs, intersections, primitives, constraints, named props, index signatures, sequences etc.
If you know your type will never include e.g. an index signature you can avoid certain cases but to be type safe obviously we have to include all thatThe schema will always be static and known at compile time but I hope to make the UI components generic over any schema and hopefully as type-safe as possible
What about
myType.internal.flatRefs
does that help?Interesting, it might be easier to iterate through these regexes to get schema properties rather than do it myself? There are so many ways I could do this now that I'll have to think about it haha
I mean if it's a JSON structure can you just assume undefined will never be in it and only allow null haha?
I was hoping not to use anything internal if I could help it for future proofing concerns but also I couldn't really figure out just by looking at the docs how to do this most efficiently
Yes, only nulls
Well your original concern was
(string | undefined)[]
but that couldn't happen so why not just use get and exclude undefined?You make a very good point :) I do control the schema so I could just remove any undefined
I thought maybe at some point I would have a reason to have optional properties
You can have optional properties but know that
undefined
will never actually be a possible value of a present key
Like JSONOh yes that would be the case so
(string | undefined)[]
would actually be impossible
Well my initial example wasn't actually useful for my exact case but this conversation has given me a lot of ideas so it was worth asking 😂You're right there are tons of ways you could do it because the internal type nodes are very introspectable. Maybe the best approach would be to decide the API you want first and exactly how you want the types to behave, then figure out how to get the values to do that
Thanks for the help!
Excited to see what you cook up!