gajus / flow-runtime

A runtime type system for JavaScript with full Flow compatibility.
MIT License
802 stars 57 forks source link

Support JSON-Schema #23

Open phpnode opened 7 years ago

phpnode commented 7 years ago

It should be possible to serialize / deserialize types to / from JSON-Schema. That is -

Given a JSON Schema object I should be able to turn it into flow runtime Type instances (which can then be turned into static type definitions via .toString()).

Given some Types, I should be able to generate a JSON Schema document.

I don't know if it will be possible to do this "losslessly" as JSON Schema includes some meta data that we don't e.g. labels and descriptions, but things like validation rules should be transferable as the validators in flow-runtime-validators are based on revalidator which uses JSON Schema.

cc @STRML because I think you were looking into this a while ago?

SukantGujar commented 7 years ago

This would be awesome when working with open source baas like loopback which enjoy a wide adoption in the JS community, but don't support webpack that well for client frameworks. This way one can use flow-runtime on the client while still keeping a single source of truth for application Model schemas as JSONs.

STRML commented 7 years ago

I ended up building https://github.com/STRML/json-to-flow which is (imo) pretty hacky because it just uses templates, it doesn't actually build an AST. It takes Swagger (not JSON-Schema) output and uses it to populate templates.

However, it appears you've actually implemented data-to-Flow via Type#toString()? In that case all we'd really need is another module or two to do Swagger & JSON-Schema input to populate those types, which wouldn't be much more than twiddling fields. Doesn't sound too hard.

bluepnume commented 7 years ago

This would be really great. 👍

jefffriesen commented 7 years ago

I would also love to see this. Being able to define your schema and then deriving everything else with it is so fun. I do this right now with JSON Schema, tcomb-json-schema and babel-plugin-tcomb.

For example, to define a form-based application, I can do this (pardon the length of this):

import {typesFromSchema} from '../utils/'
const jsonSchemaStandard = 'http://json-schema.org/draft-04/schema#'

export const FIELDS = {
  evBaseCarMpg: {
    $schema: jsonSchemaStandard,
    title: 'Base Car MPG',
    type: 'integer',
    minimum: 0,
    maximum: 100,
    defaultValue: 30
  },
  eeImprUpgradeCost: {
    $schema: jsonSchemaStandard,
    title: 'Energy Efficiency Total Upgrade Cost',
    subtitle: 'Cost without the heat pump upgrade',
    type: 'integer',
    minimum: 0,
    maximum: 50000,
    defaultValue: 10000
  },
  buildingBaseYearlyKwh: {
    $schema: jsonSchemaStandard,
    title: 'Base Yearly Electricity (kWh)',
    subtitle: 'Use metrics pane in Snugg Pro',
    type: 'integer',
    minimum: 0,
    maximum: 50000,
    defaultValue: 8000
  }
}
export const FieldDefinitionTypes = typesFromSchema(FIELDS)

From these JSON schemas, I define the forms and the interactive charts. typesFromSchema is defined as:

import _ from 'lodash'
import tcombTransform from 'tcomb-json-schema'

// Convert an object of JSON schemas to tcomb refinement types
export function typesFromSchema(fields: Object): Object {
  return _.reduce(fields, (acc, field, key) => {
    acc[key] = tcombTransform(field)
    return acc
  }, {})
}

This generates precise types (refinement types) that I can validate fields with.

// Import refinement types that were created from the JSON schema.
// For example, it gives an integer with a min and max
import {FieldDefinitionTypes as T} from '../../constants/field-definitions'
import { validate } from 'tcomb-validation'

...
  onInputChange = (e) => {
    e.preventDefault()
    const {valueAccessor} = this.props
    this.setState({
      localVal: e.currentTarget.value,
      hasError: !validate(e.currentTarget.value, T[valueAccessor]).isValid()
    })
  }

On top of all of that, I have the type checking that Flow provides, but even more precise because of the refinement types. The only thing missing is automatic generative testing like clojure.spec.

The reason for interest in flow-runtime instead of just sticking with tcomb is because it doesn't look like there's much future: https://github.com/gcanti/tcomb-json-schema/issues/24 https://github.com/facebook/flow/issues/2625#issuecomment-253482871 https://github.com/gcanti/babel-plugin-tcomb/pull/112#issuecomment-257542663

Maybe his new direction is the right one, but it's uncertain.

Adding JSON schema (and especially refinement types from JSON schema) in flow-runtime can immediately unlock a lot of use cases for the library.