-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recursive types #9
Comments
Definitely. Also want to add The 3 of them can be added at any point in a minor release. Additionally, I may also look into let User = t.object({ ... });
// Option 1
let Post = t.object({
title: t.string(),
author: t.ref(User)
// ...
});
// Option 2
let Post = t.object({
title: t.string(),
author: t.ref('user'),
// ...
}, {
$defs: {
user: User,
}
}) The first would auto-generate let User = t.object({ ... });
let Post = t.object({
title: t.string(),
author: t.ref(User)
// ...
}, {
$defs: false,
});
//-> generates object type w/ "author.$ref" pointing to a disconnected/undefined definition... but you chose that |
I think I'm going to go with Option 1 (auto-build I'm still undecided on what the // spec example:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"children": {
"type": "array",
"items": { "$ref": "#" }
}
}
}
// Option 1: emulate typebox
let person = t.recursive(self => {
return t.object({
name: t.string(),
children: t.array(self),
});
});
// Option 2: named helper
let person = t.object({
name: t.string(),
children: t.array(
t.self(), // <<
),
});
// Option 3: If t.ref() accepts strings...
let person = t.object({
name: t.string(),
children: t.array(
t.ref('#'), // <<
),
});
// Option 4: Add generic unsafe/raw method
// -> general-purpose escape hatch, you = responsible
let person = t.object({
name: t.string(),
children: t.array(
t.raw({ $ref: '#' }) // <<
),
}); I really dont like Option 1, but I wanted to include it for completeness. It'd be the only method that requires a callback and it'd be more annoying to pick up thru @dearlordylord Thoughts? |
To the first comment:
To the second comment: Options 3 and 4 don't seem to be what you need (again, assuming it's a DSL abstracting JSON schema away). Option 1 seems to be what a couple of libraries do, which makes API more understandable to users (and also, there may be reasons why they do it this specific way!). Ref: rescript-schema (.recursive), Typebox (.Recursive). Some facts that may or may not be relevant, if you want to check them out: Zod and valita have .lazy() API that doesn't pass "self" but also requires you to duplicate runtime and type definitions - that isn't perfect although in my opinion I don't care about duping types, as long as "it compiles = it works". But this approach, if used, may undermine your "tschema infers types" argument. UPD: adding arktype code for same structure just to show how cool it is const fileSystem = scope({
filename: '0<string<255',
file: {
type: "'file'",
name: 'filename',
},
directory: {
type: "'directory'",
name: 'filename',
children: [
'root[]',
':',
(v, ctx) => {
if (new Set(v.map((f) => f.name)).size !== v.length) {
return ctx.mustBe('names must be unique in a directory');
}
return true;
},
],
},
root: 'file|directory',
}).resolve('root'); |
Rerucsive types are useful and also supported by JSON schema https://json-schema.org/understanding-json-schema/structuring#recursion.
In other parser library APIs, this feature is often called "lazy".
Typebox calls it
.Recursive
.an example of a recursive structure:
The text was updated successfully, but these errors were encountered: