Creating default object from type definition

Hi! I would like to migrate my project from TypeBox to ArkType, as I love the work you're doing on it, and the development experience seems much better. There is one feature that I love in TypeBox, but couldn't find in ArkType: creating a default object from a type definition. Is this something that exists or was ever raised/considered? I would find this feature very useful to create the initial state of a form, without any code duplication. Example syntax:
import { type } from 'arktype';

export const userType = type({
name: 'string',
device: {
platform: "'android'|'ios'",
'version?': 'number',
},
});

const user = userType.create();
console.log(user)
// {name: '', device: {platform: 'android'}}
import { type } from 'arktype';

export const userType = type({
name: 'string',
device: {
platform: "'android'|'ios'",
'version?': 'number',
},
});

const user = userType.create();
console.log(user)
// {name: '', device: {platform: 'android'}}
13 Replies
ssalbdivad
ssalbdivad3mo ago
Haha it's funny you should say this. It was actually a feature in the very earliest versions of ArkType but eventually I decided it was out of scope for a validator. It's easy enough to do for simple keywords like string or literals like android, but precisely generating e.g. values that satisfy an arbitrary regex is not trivial. As far as testing, @ArkShawn is currently working on fast-check integration which will be great for generating values from your data like this: https://github.com/arktypeio/arktype/issues/502 As far as defaults, it seems tough to cohesively define what it would mean to generate default values for "simple cases" while somehow bailing (probably would have to throw) for stuff like regex generation that is out of scope. Interested if you have ideas though
itsyoboieltr
itsyoboieltrOP3mo ago
I think it would be a great first step to support the primitive, simple use-cases, which is what most people would need this feature for. I think for the use-case I am talking about, it would be relatively simple to make something work. I would propose that we ignore regexes, length, and all the other stuff, as the generated default value is not supposed to be correct (does not need to pass type validation), as I expect the user in the frontend to update this value to make it correct at the time of submission. This is the original code, basically the reason why this feature should be implemented:
import { type } from 'arktype';

export const user = type({ name: 'string' });

export const getDefaultUser = () => ({ name: '' });
import { type } from 'arktype';

export const user = type({ name: 'string' });

export const getDefaultUser = () => ({ name: '' });
Let's say I update the user type's name property to match a regex pattern. I would still want the default user's name to have a value of an empty string, as the user will need to enter his name. The generated default name does not need to match the regex pattern.
import { type } from 'arktype';

export const user = type({ name: /^([A-Za-z]+)\s([A-Za-z]+)$/ });

export const getDefaultUser = () => ({ name: '' });
import { type } from 'arktype';

export const user = type({ name: /^([A-Za-z]+)\s([A-Za-z]+)$/ });

export const getDefaultUser = () => ({ name: '' });
Similar for length, and other properties:
import { type } from 'arktype';

export const user = type({ name: 'string > 10' });

export const getDefaultUser = () => ({ name: '' });
import { type } from 'arktype';

export const user = type({ name: 'string > 10' });

export const getDefaultUser = () => ({ name: '' });
I wouldn't expect a random 10 character string to be generated here. (By the way, in TypeBox, this would generate: 'aaaaaaaaaa') So maybe a practical solution is easier than you first thought? Of course, if your goal is to create a default value that is 100% according to the type and passes validation, then it can get complex pretty quick. But most of the time, default values are not expected to conform to the type, as they are supposed to be updated later (in the use-case of generating initial state for a form). so a good rule of thumb would be to respect the primitives, but ignore the spicy constraints (in my opinion) for default generation
ssalbdivad
ssalbdivad3mo ago
(By the way, in TypeBox, this would generate: 'aaaaaaaaaa')
This is exactly the kind of thing I started to get into that made me think this kind of generation doesn't belong in the core library. This wouldn't be useful as a default as you say, only for testing, and at that point, it's better to just delegate to a library like fast-check. So what you're proposing I guess would be something like .shapeOf. .default is already a method that adds a default value that must satisfy the base type so I'd definitely want to distinguish it from that. I do buy that as more reasonable and it would be easy to implement, but I guess there's still quite a lot that feels arbitrary. What would you generate for a union? I definitely don't want anything non-deterministic but unions are unordered I suppose arrays would always be empty. It's so funny I remember the constraints I wrote for this kind of stuff years ago associating each value kind with its default
itsyoboieltr
itsyoboieltrOP3mo ago
I would expect arrays to be empty yeah. About unions I am unsure though...
ssalbdivad
ssalbdivad3mo ago
I remember what I did actually for unions was I had a precedence table of how ideal a given branch was as a default. So it would favor simple types like null over objects etc.
itsyoboieltr
itsyoboieltrOP3mo ago
yeah, exactly. If something is optional, it shouldn't exist, if something is nullable, it should be null, etc.
ssalbdivad
ssalbdivad3mo ago
Yeah I could honestly go digging through the git history and probably find a set of rules I put a lot of thought into for that stuff Well I'm definitely open to the idea that some kind of shape could be valuable. My original motivation back then for AT was an auto-validated store so definitely see the benefit there Still a bit awkward that the "shape" wouldn't satisfy the underlying type in many cases and I'd have to probably create some set of conditonals to try and infer out what it would actually be for a given type
itsyoboieltr
itsyoboieltrOP3mo ago
Maybe a better description instead of default would be something like: "simplest representation"
ssalbdivad
ssalbdivad3mo ago
Not sure I'd have to think about it I'd still think it would satisfy the type The naming isn't really the important part though it's having a simple consistent set of rules for how all types are handled. Stuff that seems superifically simple can be complex since ArkType supports a lot like cyclic types If you want to draft up an issue with some of these ideas though I'd definitely be willing to consider it further. In the meantime, it should be fairly simple to implement a helper function externally that does what you want Since you know your exact requirements, you can make assumptions I can't make if I want it to be a fully integrated feature
itsyoboieltr
itsyoboieltrOP3mo ago
true but I guess this would also be useful for others, so I'll make an issue about it and we'll see
ssalbdivad
ssalbdivad3mo ago
Yeah I agree it could be
itsyoboieltr
itsyoboieltrOP3mo ago
thanks for taking your time to answer and provide detailed answers!
ssalbdivad
ssalbdivad3mo ago
I found some of the old generate code. Don't know how good all of it is, but I definitely remember thinking a lot about union precedence so if you can distill something from that it might be worth including in the issue. These are the tests at that time: https://github.com/arktypeio/arktype/blob/9440417d93dd789720229a64d59247301250abc9/pkgs/model/src/__tests__/generate.test.ts
GitHub
arktype/pkgs/model/src/tests/generate.test.ts at 9440417d93dd78...
TypeScript's 1:1 validator, optimized from editor to runtime - arktypeio/arktype
Want results from more Discord servers?
Add your server