A
arktype3mo ago
PIat

Dynamic type based on another value

Given a theoretical example like this:
const formDef = type({
agree: 'boolean',
clauses: ['===', 'first', 'second'],
})
const formDef = type({
agree: 'boolean',
clauses: ['===', 'first', 'second'],
})
Is it possible to only have clauses be required if agree is true?
13 Replies
Dimava
Dimava3mo ago
Or of a type with "true" And a type with false
PIat
PIatOP3mo ago
AAAHHHH Narrowing 🫠 Thank you!
TizzySaurus
TizzySaurus3mo ago
Not even that. Just use a discriminated union, like you would with raw TS.
// raw TS
type t = {agree: true, clauses: "first" | "second"} | {agree: false, clauses?: "first" | "second"

// arktype
const t = type({agree: true, clauses: "'first' | 'second'"}).or({agree: false, clauses?: "'first' | 'second'"})
// raw TS
type t = {agree: true, clauses: "first" | "second"} | {agree: false, clauses?: "first" | "second"

// arktype
const t = type({agree: true, clauses: "'first' | 'second'"}).or({agree: false, clauses?: "'first' | 'second'"})
PIat
PIatOP3mo ago
Thank you 🙏🙏🙏
dibbo
dibbo3mo ago
Is there an alternative approach to this available via arktype at all? I have run into a similar scenario but with many possible key/value combinations. Rather than writing a specific type for each scenario and oring them, can I configure a property to be required/optional based on the value(s) of other properties?
Dimava
Dimava3mo ago
Can you provide an example? (with 5+ types)
dibbo
dibbo3mo ago
Sure, 2 secs
const example = type({
status: "'Available'|'Unavailable'|'Reclaimed'|'Needs Reclaimed'",
species: "'Dog'|'Cat'|'Other'",
"dateOfBirth?": "Date", // Should be required when status is Available|Unavailable
"breed?": "string", // Should be required when species is Dog
"crossBreed?": "string", // Optional but only to be supplied when species is Dog
"summary?": "string", // Required when status is Available|Needs Reclaimed
});
const example = type({
status: "'Available'|'Unavailable'|'Reclaimed'|'Needs Reclaimed'",
species: "'Dog'|'Cat'|'Other'",
"dateOfBirth?": "Date", // Should be required when status is Available|Unavailable
"breed?": "string", // Should be required when species is Dog
"crossBreed?": "string", // Optional but only to be supplied when species is Dog
"summary?": "string", // Required when status is Available|Needs Reclaimed
});
TizzySaurus
TizzySaurus3mo ago
I think you've got to do effectively the same thing There might be some logic with scopes you can use, but not certain
Dimava
Dimava3mo ago
Do you need that in types or in runtime only?
dibbo
dibbo3mo ago
Runtime (I think - its used to validate incoming request bodies) Although thinking about it, I would also like to use it as a Type in the application, so I'm guessing I'll have to do the oring
Dimava
Dimava3mo ago
Okay it's a hard question
import { scope, Type } from 'arktype'

function oneOf<const defs>(
defs: scope.validate<defs>
): Type<scope.infer<defs>[keyof scope.infer<defs>]> {
let s = scope({...defs, _result: Object.keys(defs).join('|') as any })
return s.export()._result as any
}

let T = oneOf({
dog: { species: "'Dog'", "breed": "string", "crossBreed?": "string" },
cat: { species: "'Cat'" },
other: { species: "'Other'" },
}).and({ "breed?": "string" })
.onUndeclaredKey("reject")
import { scope, Type } from 'arktype'

function oneOf<const defs>(
defs: scope.validate<defs>
): Type<scope.infer<defs>[keyof scope.infer<defs>]> {
let s = scope({...defs, _result: Object.keys(defs).join('|') as any })
return s.export()._result as any
}

let T = oneOf({
dog: { species: "'Dog'", "breed": "string", "crossBreed?": "string" },
cat: { species: "'Cat'" },
other: { species: "'Other'" },
}).and({ "breed?": "string" })
.onUndeclaredKey("reject")
ssalbdivad
ssalbdivad3mo ago
Yeah if you want it inferred in TS you probably have to do something similar to what you'd do in TS (you can use key?: never explicitly if you want for keys to help TS discriminate). Otherwise I'd say for something like this if you didn't care about having a narrowed type or already had one defined, you could always just write a .narrow function with arbitrary logic. This is so overly convoluted compared to (acc, t) => acc.or(t) for creating a union That is if you really need to avoid chaining in the first place Which you definitely don't in a case like this
dibbo
dibbo3mo ago
As always, thank you for the help 🙏
Want results from more Discord servers?
Add your server