Skip to content
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

Convert to JSON Schema #197

Open
xogeny opened this issue Aug 13, 2018 · 6 comments
Open

Convert to JSON Schema #197

xogeny opened this issue Aug 13, 2018 · 6 comments
Milestone

Comments

@xogeny
Copy link

xogeny commented Aug 13, 2018

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.

@gcanti
Copy link
Owner

gcanti commented Aug 14, 2018

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

@julienrf
Copy link

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:

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

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

@Ciantic
Copy link

Ciantic commented May 7, 2019

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
})

@cyberixae
Copy link

cyberixae commented Oct 2, 2019

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

@cyberixae
Copy link

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

@ljani
Copy link

ljani commented Apr 12, 2022

NB. there's an example in the repo, which uses Schema to convert to JSON schema. It works wonderfully. However, it is using the older json-schema package, but it's easy to port to eg. openapi3-ts.

I'm wondering how one should add eg. the aforementioned description or examples fields? It's easy to do for primitives, but if you want to use it for more complex types, it gets harder. (The same goes for decoder's withMessage.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants