I need to compare runtime types, i.e. understand whether two types are identical, one a sub-type of the other, or incompatible. It seems that no straightforward way to achieve this was built into the library, or am I missing something?
I'd like to create a function like:
function isSubtype(subtype: Type<A>, supertype: Type<B>): boolean
Currently I can look at the name property, which is good for basic types (e.g. t.string.name === 'string') but must be parsed when dealing with complex types (e.g. t.array(t.boolean).name === 'Array<boolean>'). Any better option?
typescript uses structural typing, so, you must iterate over the fields and compare them.
This is an example of the object's type traversal:
type ObjectType<T extends t.Props> = t.InterfaceType<T> | t.PartialType<T>
function traverseObjectType<T>(typ: t.Type<T>, f: (type: ObjectType<any>) => t.Type<any>): t.Type<T> {
if (TU.isUnionType(typ)) {
return t.union(typ.types.map((v: t.Type<any>) => traverseObjectType(v, f)), typ.name)
} else if (TU.isReadonlyType(typ)) {
return t.readonly(traverseObjectType(typ.type, f), typ.name) as any
} else if (TU.isRefinementType(typ)) {
return t.refinement(traverseObjectType(typ.type, f), typ.predicate, typ.name) as any
} else if (TU.isInterfaceType(typ) || TU.isPartialType(typ)) {
return f(typ)
} else if (TU.isIntersectionType(typ)) {
return t.intersection(typ.types.map((v: t.Type<any>) => traverseObjectType(v, f)), typ.name)
} else if (TU.isArrayType(typ)) {
return t.array(traverseObjectType(typ.type, f), typ.name) as any
} else if (TU.isDictionaryType(typ)) {
return t.dictionary(typ.domain, traverseObjectType(typ.codomain, f), typ.name) as any
}
return typ
}
Thank you!
TU is yours, right? How did you implement TU.isInterfaceType(typ), for instance?
export function isUnionType(type: t.Type<any>): type is t.UnionType<any, any> {
return (type as any)._tag === "UnionType"
}
export function isInterfaceType(type: t.Type<any>): type is t.InterfaceType<any> {
return (type as any)._tag === "InterfaceType"
}
export function isPartialType(type: t.Type<any>): type is t.PartialType<any> {
return (type as any)._tag === "PartialType"
}
export function isReadonlyType(type: t.Type<any>): type is t.ReadonlyType<any> {
return (type as any)._tag === "ReadonlyType"
}
export function isIntersectionType(type: t.Type<any>): type is t.IntersectionType<any, any> {
return (type as any)._tag === "IntersectionType"
}
export function isArrayType(type: t.Type<any>): type is t.ArrayType<any> {
return (type as any)._tag === "ArrayType"
}
export function isDictionaryType(type: t.Type<any>): type is t.DictionaryType<any, any> {
return (type as any)._tag === "DictionaryType"
}
export function isRefinementType(type: t.Type<any>): type is t.RefinementType<any> {
return (type as any)._tag === "RefinementType"
}
Perfect, I'll use your code. Many thanks!
It would be nice that this sort of inspections were officially supported, through a public API. Maybe _tag could be made public. Any thoughts?
_tags are public but undocumented at the moment, I'll gladly accept a PR (even better if it contains a real world example like yours)
@abaco Did you already implement something like isSubtype, mentioned in your question? If so, would you mind to share it, as I'm also considering to extend io-ts with such a functionality. Thanks!
@fabiandev Actually I focused on other priorities and now I'm not sure anymore whether it makes sense to go that way or not, at least for my use case. Besides comparing runtime types I also need to serialize them, and since t.Type can't be serialized directly (because of the validate function) I'm using a JSON representation, that I'm then going to convert to/from t.Type<*>. At that point it's maybe easier for me to compare the JSON representations. At the end of the day I don't have a "real world example" that justifies this development.
@abaco Fair enough. I'm also currently looking into runtime type comparison through serialized JSON data. I'll leave a comment once I've made some progress.
@fabiandev @abaco I know it's been quite a while, but have anyone of you made progress on this? Looking into the same issue..
@antoniusostermann I've created ts-runtime a while ago which uses flow-runtime as runtime type check library, though. Unfortunately I haven't been able to invest more time after the the initial POC was done.