Io-ts: Convert to JSON Schema

Created on 13 Aug 2018  路  5Comments  路  Source: gcanti/io-ts

I'm considering a use case where I'd like to export JSON Schema for interoperability with other tools or languages. Looking through the definition for Type, it seems like every instance defines a member named _tag. However, _tag is not part of Type. I realize that all these various types are never combined into a union type, so it might seem to be useless. But if I'd like to do this for some known subset of types, it seems useful to be able to access _tag in some type safe way.

The closest I seem to be able to come is to define a type like this:

type All = t.InterfaceType<any> | t.NumberType | ... /* all JS built-in types */;

In this case, _tag is public in all of these so All becomes a tagged union and I could switch on all the necessary cases and extract all the type information I need. Is this a reasonable approach? Is there a better one? BTW, I looked over #53 and, frankly, I could not understand most of what was being discussed in that thread.

Thanks.

Most helpful comment

I really like the approach of using an io-ts runtime type as the source of truth, but I would be missing a few features to cover my use case. Specifically, I need to be able to define a description for my types:

~ typescript
t.type({
lat: t.number,
lon: t.number
}).description('Geographical coordinates (WGS84 format)')
~

Would you be interested in supporting such descriptions in io-ts?

All 5 comments

AFAIK there are possible 2 approaches

  • convert a runtime type to JSON Schema (either at runtime or compile time)
  • convert a JSON Schema to a runtime type (either at runtime or compile time)

In the first case (i.e. if your source of truth is a io-ts runtime type) I'd go for a tagged union like you said

A partial implementation

interface JSONSchemaArrayRuntimeType extends t.ArrayType<JSONSchemaRuntimeType> {}
type JSONSchemaObjectRuntimeType = t.InterfaceType<{ [key: string]: JSONSchemaRuntimeType }>
type JSONSchemaRuntimeType =
  | t.StringType
  | t.NumberType
  | t.BooleanType
  | JSONSchemaArrayRuntimeType
  | JSONSchemaObjectRuntimeType

export const toJSONSchema = (type: JSONSchemaRuntimeType): JSONSchema => {
  switch (type._tag) {
    case 'StringType':
      return { type: 'string' }
    case 'NumberType':
      return { type: 'number' }
    case 'BooleanType':
      return { type: 'boolean' }
    case 'ArrayType':
      return { type: 'array', item: toJSONSchema(type.type) }
    case 'InterfaceType':
      return {
        type: 'object',
        properties: {
          /* TODO */
        }
      }
  }
}

Note that you can try to prevent illegal conversions statically

toJSONSchema(
  t.type({
    foo: t.undefined
  })
) // static error

However conversions might become tricky if there are recursive runtime types involved.

The second option would be to go from a json schema to a runtime type, likely with code generation. In this case you may want to take a look at io-ts-codegen and specifically to this (partial) example

I really like the approach of using an io-ts runtime type as the source of truth, but I would be missing a few features to cover my use case. Specifically, I need to be able to define a description for my types:

~ typescript
t.type({
lat: t.number,
lon: t.number
}).description('Geographical coordinates (WGS84 format)')
~

Would you be interested in supporting such descriptions in io-ts?

I was looking for JSSchema myself too.

Description is a bit specific, but could there be a way to associate meta data?

t.type({
  lat: t.number,
  lon: t.number
}).metaData({ 
  description: 'Geographical coordinates (WGS84 format)', 
  name: "Coordinates" 
})

So it could contain anything, and be used by other iterators. I'm also looking a way to turn io-ts definitions to SQL table definitions, so meta data would be helpful there too.

Naturally it should work with numbers etc too, like

t.type({
  lat: t.number.metaData({ name: "Latitude", description: "..."}),
  lon: t.number
})

I'm experimenting with converting JSON schemas to io-ts. I think it makes sense to do it this way since JSONSchema is simpler than io-ts validators which are turing complete. In other words it is relatively easy to check minLength in an io-ts refinement but it is nearly impossible to check an arbitrary refinement with JSON schema. Therefore it makes sense to describe the language independent part as a JSON schema and translate it to language specific validators.

See converter script, example input, example output

I wrote a minimalistic CLI and moved the converter script into a separate package to make it a tiny bit more reusable. See https://www.npmjs.com/package/io-ts-from-json-schema

Was this page helpful?
0 / 5 - 0 ratings

Related issues

spacejack picture spacejack  路  3Comments

mottetm picture mottetm  路  5Comments

gunzip picture gunzip  路  9Comments

steida picture steida  路  3Comments

gcanti picture gcanti  路  10Comments