Comments (7)
@sdc395 Hi,
Would support for definitions solve this? Is there a workaround that would allow me to export a single schema containing references to recursive types from a package?
Not necessarily, but let me explain.
Many versions ago, TypeBox included a Type.Namespace
type that produced a $defs
schematic. This type was removed however as the construction of $defs varied widely among json schema users, and anything TypeBox introduced was too narrow to meet ALL end user requirements. Many users were experiencing issues trying to port $defs schematics to Type.Namespace
and finding out the referencing paths, and structures did not fit the type.
As such, a call was made to remove Type.Namespace
in the short term which forced users to build out their own $defs
and wrestle with Type.Ref, String Ref, and out of order definitions (things that TypeBox would have otherwise had to wrestle with internally). This decision was mostly prompted due to the uncertainty on how to appropriately handle $defs (with many of your questions here and recently noted on #882 being pressing questions internal to the library also)
All this said, I am planning to reintroduce Type.Namespace
. As before, this Type would generate $defs schematics, however the type would be documented as being "extremely opinionated" and "specific to TypeBox". This following is the high level idea.
// Model generates referential $id based on property name
const Model = Type.Namespace({
Name: Type.String(),
Person: Type.Object({
name: Type.Ref('Name') // reference the Name type
})
})
// Infer This Way
type Model = Static<typeof Model>
type Name = Model['Name']
type Person = Model['Person']
// Get Schematics This Way
const Name = Type.Index(Model, 'Name')
const Person = Type.Index(Model, 'Person')
// Or this way
const Name = Model.$defs.Name
const Person = Model.$defs.Person
Keep in mind, the above probably wouldn't solve the json-schema-to-typescript
issue noted (as for the above to work, TypeBox is heavily reliant on internal tracked identifiers, and linear / flat structure of the $defs, and cannot predict in advance what exterior tools will generate). But there will be something to map into if you're prepared to follow TB conventions.
Hope this brings some insight
Cheers
S
from typebox.
I've just realised that the involvement of a recursive type was an assumption of mine. The following causes the same issue...
import { Type } from '@sinclair/typebox';
import { TypeCompiler } from '@sinclair/typebox/compiler';
const Vector = Type.Object({
x: Type.Number(),
y: Type.Number(),
}, { $id: 'Vector' });
const VectorRef = Type.Ref(Vector);
TypeCompiler.Compile(VectorRef);
Does TypeCompiler not support references?
from typebox.
OK, so I had assumed that TypeCompiler had magical access to the referenced type. I'd somehow failed to notice the references
optional argument to TypeCompiler.compile
. Providing the Vector
schema to TypeCompiler.Compile(VectorRef)
fixes my error.
That said, there still seems to be an issue with recursive types.
import { Type } from '@sinclair/typebox';
import { TypeCompiler } from '@sinclair/typebox/compiler';
const Vector = Type.Object({
x: Type.Number(),
y: Type.Number(),
}, { $id: 'Vector' })
const VectorRef = Type.Ref(Vector)
const VectorDeref = Type.Deref(VectorRef, [Vector]);
// OK
TypeCompiler.Compile(VectorDeref);
const Node = Type.Recursive(Node => Type.Object({
id: Type.String(),
nodes: Type.Array(Node),
}, { $id: 'Node' }));
const NodeRef = Type.Ref(Node);
const NodeDeref = Type.Deref(NodeRef, [Node]);
// TypeDereferenceError: Unable to dereference schema with $id 'undefined'
TypeCompiler.Compile(NodeDeref);
from typebox.
@sdc395 Hi,
Please ignore this non-issue. I had assumed that TypeCompiler had magical access to the referenced type. My bad.
That's right, Ref types are string referenced aliases to some target type. To validate a type containing a reference type, you will need to inform the compiler about the target type so it can be dereferenced. There's a couple of way to achieve this. Consider for the provided Vector type.
const Vector = Type.Object({
x: Type.Number(),
y: Type.Number(),
}, { $id: 'Vector' })
const VectorRef = Type.Ref(Vector)
Option A: Pass references array to Compile
Most TypeBox functions have an overloaded function to pass a references
array. The types passed on the array should all have $id
identifiers such that the Ref can target it.
TypeCompiler.Compile(VectorRef, [Vector])
// similarly you can pass references to Value.Check
Value.Check(VectorRef, [Vector], { x: 1, y: 2 })
Option B: Defer target type
In 0.32.0, TypeBox added the Deref type which will return a de-normalized type. The following uses Deref to deference the target type meaning you don't need to pass the references array on Compile.
TypeCompiler.Compile(Type.Deref(VectorRef, [Vector]))
Auto Tracking References
In both cases above, you will need to carry around the reference + target types, however there are abstractions you can write over the top of Compile to auto track $id / $ref. The following is a quick implementation of this.
import { Type, TSchema } from '@sinclair/typebox'
import { TypeCompiler, TypeCheck } from '@sinclair/typebox/compiler'
// ------------------------------------------------------------------
// Auto Tracking References
// ------------------------------------------------------------------
const references: TSchema[] = []
let ordinal = 0
// wraps a type and ensures it has an $id. The type is added to the references array.
function Target<T extends TSchema>(schema: T): T {
if('$id' in schema) {
references.push(schema)
return schema
} else {
const remapped = { $id: `type-${ordinal++}`, ...schema }
references.push(remapped)
return remapped
}
}
// custom compile method
function Compile<T extends TSchema>(schema: T): TypeCheck<T> {
return TypeCompiler.Compile(schema, references)
}
// ------------------------------------------------------------------
// Example
// ------------------------------------------------------------------
const Vector = Target(Type.Object({
x: Type.Number(),
y: Type.Number(),
}))
const VectorRef = Type.Ref(Vector)
Compile(VectorRef)
Hope this helps
S
from typebox.
Thank you for your response to my confusion. I'll read through it and try to get a grip. :-)
from typebox.
@sdc395 Hey
Will close off this issue as you will need to pass references arrays on various functions if using Ref. TypeBox will deference types if you supply the references array on various functions, however tracking references is something that must be handled by the implementer (as there's a few ways to approach it). You can use the "Auto Tracking References" above as a starter.
Cheers!
S
from typebox.
With regard to my "Vector versus Node" code above, I'm guessing the problem comes down to the way references are unpacked into duplicates of the referenced schema (an assumption based on the documentation of Type.Deref
).
Would support for definitions solve this? Is there a workaround that would allow me to export a single schema containing references to recursive types from a package?
from typebox.
Related Issues (20)
- Should `IsValueType` guard consider `Date`? HOT 1
- ESNext target HOT 3
- Trying to do codegen from Drizzle schemas HOT 1
- Make `default` and `examples` schema options properties type-safe HOT 2
- Indicate which files don't have "sideEffects" in package.json for improved tree shaking HOT 3
- Type.String().Optional() is accepted on ts-hint / ts-lint, and even build successfully; but of course, "Optional()" does not exists HOT 2
- Dynamic Template Literals can't be Mapped HOT 2
- How should TypeBox types be used with TS type predicates? HOT 2
- TypeCompiler.Compile incorrectly parsing `Record<any, any>` HOT 7
- Error when using Composite with Ref (Unable to dereference schema with $id 'undefined') HOT 3
- Transform type keys in Record HOT 2
- Is it possible to add support to the Type.String additional properties for the enum keyword HOT 2
- Support for async validations? HOT 1
- Transform + Map/Index does not work HOT 4
- Update the error message for required properties HOT 2
- Covariance issue with `TypeCheck<TSchema>` and `TypeCheck<TObject<{}>>` HOT 1
- Default values in nested objects are not generated HOT 2
- Doubt on Record HOT 2
- TypeCompiler compile on Type.Strict(T) - Preflight validation check failed to guard for the given schema HOT 2
- Max call stack size exceeded
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from typebox.