A
arktype•2mo ago
noxy

Iterate over schema

Hello, I'm trying to create dynamic forms that would use ArkType to dynamically generate correct input type, but somehow I cannot find any good way to iterate over the schema. I've tried few things, and the only one that works is accessing schema.json.required which is not even part of TS type of JSON (it just exists at runtime). Is there any easier way? Basically what I'm looking for is for a way to turn some schema
const schema = type({
email: "string.email",
name: "string",
checkboxVal: "boolean",
numVal: "number",
color: type("string").configure({
inputType: "color",
}),
});
const schema = type({
email: "string.email",
name: "string",
checkboxVal: "boolean",
numVal: "number",
color: type("string").configure({
inputType: "color",
}),
});
Into some representation that I can use to render components dynamically like
[
{key: "email", value: "string.email"},
{key: "name", value: "string"},
{key: "checkboxVal", value: "boolean"},
{key: "numVal", value: "number"},
{key: "color", value: "string", meta: { inputType: "color" } },
]
[
{key: "email", value: "string.email"},
{key: "name", value: "string"},
{key: "checkboxVal", value: "boolean"},
{key: "numVal", value: "number"},
{key: "color", value: "string", meta: { inputType: "color" } },
]
Am I missing something? I've tried to look for solutions on the web, discord and LLMs but didn't find anything useful 😆
10 Replies
noxy
noxyOP•2mo ago
Actually maybe getting separate validators for each iterated field could be useful so each input would have it's own error message. Not sure if that's possible in ArkType though.
ssalbdivad
ssalbdivad•2mo ago
Hey! Totally not your fault for not being able to find anything. Iterating over keys like this is not yet a well documented part of the API, though I'm working on adding some content on it soon! What you're looking for is the .props property that will exist on an object literal and include the key as a string and value as a Type instance. You wouldn't necessarily be able to get something like the original definition (string.email) out, but Types give you lots of tools to introspect and make the comparisons you need.
import { type } from "arktype"

const original = type({
id: "string",
name: "string",
isAdmin: "boolean",
"favoriteColor?": "string"
})

const entries = original.props.map(prop => [prop.key, prop.value.expression])

// ["id", "string"], [ 'isAdmin', 'boolean' ], ["name", "string"], ["favoriteColor", "string"]]
console.log(entries)
import { type } from "arktype"

const original = type({
id: "string",
name: "string",
isAdmin: "boolean",
"favoriteColor?": "string"
})

const entries = original.props.map(prop => [prop.key, prop.value.expression])

// ["id", "string"], [ 'isAdmin', 'boolean' ], ["name", "string"], ["favoriteColor", "string"]]
console.log(entries)
In this case I'm using .expression which creates a string representation of the Type
noxy
noxyOP•2mo ago
That's what I was looking for. I probably would have found it, but for some reason .props is not present on generic Type<T> only on specific schema. Thank you for pointing me in the right direction.
ssalbdivad
ssalbdivad•2mo ago
It is only present on Types that extend object There are quite a few props/methods like this that are specific to object like .props or .partial or methods that only exist on e.g. strings like atLeastLength or .matching
noxy
noxyOP•2mo ago
My signature looks like
export function DynamicForm<T extends {}>(
props: ParentProps<{
schema: Type<T>;
store: T;
setStore: Setter<T>;
}>
) {
export function DynamicForm<T extends {}>(
props: ParentProps<{
schema: Type<T>;
store: T;
setStore: Setter<T>;
}>
) {
So it should be there?
ssalbdivad
ssalbdivad•2mo ago
There are some quirks with how TS evaluates this sort of thing in the context of a generic body you might run into. If the behavior is not correct, it is probably a TS bug as the logic in ArkType is essentially "if T extends object the method should be there." That said usually since it's just a single internal implementation where you have to deal with these quirks, its not so bad to cast once and have it be safe externally. A couple side notes though that could affect this: - {} technically does not imply object since it includes primitives other than null and undefined as well - You may have better luck with something like T extends object or even T extends type.Any<object> which you can experiment with if you want to try and find a way to avoid casting in the implementation
noxy
noxyOP•2mo ago
Sure, I can work around it, not a big deal (maybe it indeed is TS bug). both extends object and type.Any<object> don't change anything here
ssalbdivad
ssalbdivad•2mo ago
Yeah just messed around with it a bit it looks pretty stubborn. There may be a combination of stuff you can use to avoid casting but when you're writing generic functions like this you end up just having to cast often anyways due to limitations of TypeScript (take it from someone who has written a lot) I see it as a trade off as some lack of safety in a single implementation vs. more precise types for every invocation of that function
noxy
noxyOP•2mo ago
Yea, all good. Thanks again for pointing me in the right direction :-)
ssalbdivad
ssalbdivad•2mo ago
Oh I actually wrote a bit about this in the docs: https://arktype.io/docs/faq#why-isnt-my-wrapper-generic-working
ArkType
ArkType Docs
TypeScript's 1:1 validator, optimized from editor to runtime

Did you find this page helpful?