fluent-json-schema
    TypeScript icon, indicating that this package has built-in type declarations

    3.0.1 • Public • Published

    fluent-json-schema

    A fluent API to generate JSON schemas (draft-07) for Node.js and browser. Framework agnostic.

    view on npm Known Vulnerabilities Coverage Status code style: prettier

    Features

    • Fluent schema implements JSON Schema draft-07 standards
    • Faster and shorter way to write a JSON Schema via a fluent API
    • Runtime errors for invalid options or keywords misuse
    • JavaScript constants can be used in the JSON schema (e.g. enum, const, default ) avoiding discrepancies between model and schema
    • TypeScript definitions
    • Coverage 99%

    Install

    npm install fluent-json-schema --save
    

    or

    yarn add fluent-json-schema
    

    Usage

    const S = require('fluent-json-schema')
    
    const ROLES = {
      ADMIN: 'ADMIN',
      USER: 'USER',
    }
    
    const schema = S.object()
      .id('http://foo/user')
      .title('My First Fluent JSON Schema')
      .description('A simple user')
      .prop('email', S.string().format(S.FORMATS.EMAIL).required())
      .prop('password', S.string().minLength(8).required())
      .prop('role', S.string().enum(Object.values(ROLES)).default(ROLES.USER))
      .prop(
        'birthday',
        S.raw({ type: 'string', format: 'date', formatMaximum: '2020-01-01' }) // formatMaximum is an AJV custom keywords
      )
      .definition(
        'address',
        S.object()
          .id('#address')
          .prop('line1', S.anyOf([S.string(), S.null()])) // JSON Schema nullable
          .prop('line2', S.string().raw({ nullable: true })) // Open API / Swagger  nullable
          .prop('country', S.string())
          .prop('city', S.string())
          .prop('zipcode', S.string())
          .required(['line1', 'country', 'city', 'zipcode'])
      )
      .prop('address', S.ref('#address'))
    
    console.log(JSON.stringify(schema.valueOf(), undefined, 2))

    Schema generated:

    {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "definitions": {
        "address": {
          "type": "object",
          "$id": "#address",
          "properties": {
            "line1": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            },
            "line2": {
              "type": "string",
              "nullable": true
            },
            "country": {
              "type": "string"
            },
            "city": {
              "type": "string"
            },
            "zipcode": {
              "type": "string"
            }
          },
          "required": ["line1", "country", "city", "zipcode"]
        }
      },
      "type": "object",
      "$id": "http://foo/user",
      "title": "My First Fluent JSON Schema",
      "description": "A simple user",
      "properties": {
        "email": {
          "type": "string",
          "format": "email"
        },
        "password": {
          "type": "string",
          "minLength": 8
        },
        "birthday": {
          "type": "string",
          "format": "date",
          "formatMaximum": "2020-01-01"
        },
        "role": {
          "type": "string",
          "enum": ["ADMIN", "USER"],
          "default": "USER"
        },
        "address": {
          "$ref": "#address"
        }
      },
      "required": ["email", "password"]
    }

    TypeScript

    With "esModuleInterop": true activated in the tsconfig.json:

    import S from 'fluent-json-schema'
    
    const schema = S.object()
      .prop('foo', S.string())
      .prop('bar', S.number())
      .valueOf()

    With "esModuleInterop": false in the tsconfig.json:

    import * as S from 'fluent-json-schema'
    
    const schema = S.object()
      .prop('foo', S.string())
      .prop('bar', S.number())
      .valueOf()

    Validation

    Fluent schema does not validate a JSON schema. However, many libraries can do that for you. Below a few examples using AJV:

    npm install ajv --save
    

    or

    yarn add ajv
    

    Validate an empty model

    Snippet:

    const ajv = new Ajv({ allErrors: true })
    const validate = ajv.compile(schema.valueOf())
    let user = {}
    let valid = validate(user)
    console.log({ valid }) //=> {valid: false}
    console.log(validate.errors) //=> {valid: false}

    Output:

    {valid: false}
    errors: [
      {
        keyword: 'required',
        dataPath: '',
        schemaPath: '#/required',
        params: { missingProperty: 'email' },
        message: "should have required property 'email'",
      },
      {
        keyword: 'required',
        dataPath: '',
        schemaPath: '#/required',
        params: { missingProperty: 'password' },
        message: "should have required property 'password'",
      },
    ]
    
    

    Validate a partially filled model

    Snippet:

    user = { email: 'test', password: 'password' }
    valid = validate(user)
    console.log({ valid })
    console.log(validate.errors)

    Output:

    {valid: false}
    errors:
    [ { keyword: 'format',
        dataPath: '.email',
        schemaPath: '#/properties/email/format',
        params: { format: 'email' },
        message: 'should match format "email"' } ]
    
    

    Validate a model with a wrong format attribute

    Snippet:

    user = { email: 'test@foo.com', password: 'password' }
    valid = validate(user)
    console.log({ valid })
    console.log('errors:', validate.errors)

    Output:

    {valid: false}
    errors: [ { keyword: 'required',
        dataPath: '.address',
        schemaPath: '#definitions/address/required',
        params: { missingProperty: 'country' },
        message: 'should have required property \'country\'' },
      { keyword: 'required',
        dataPath: '.address',
        schemaPath: '#definitions/address/required',
        params: { missingProperty: 'city' },
        message: 'should have required property \'city\'' },
      { keyword: 'required',
        dataPath: '.address',
        schemaPath: '#definitions/address/required',
        params: { missingProperty: 'zipcoce' },
        message: 'should have required property \'zipcode\'' } ]
    

    Valid model

    Snippet:

    user = { email: 'test@foo.com', password: 'password' }
    valid = validate(user)
    console.log({ valid })

    Output:

    {valid: true}
    

    Extend schema

    Normally inheritance with JSON Schema is achieved with allOf. However when .additionalProperties(false) is used the validator won't understand which properties come from the base schema. S.extend creates a schema merging the base into the new one so that the validator knows all the properties because it is evaluating only a single schema. For example, in a CRUD API POST /users could use the userBaseSchema rather than GET /users or PATCH /users use the userSchema which contains the id, createdAt and updatedAt generated server side.

    const S = require('fluent-json-schema')
    const userBaseSchema = S.object()
      .additionalProperties(false)
      .prop('username', S.string())
      .prop('password', S.string())
    
    const userSchema = S.object()
      .prop('id', S.string().format('uuid'))
      .prop('createdAt', S.string().format('time'))
      .prop('updatedAt', S.string().format('time'))
      .extend(userBaseSchema)
    
    console.log(userSchema)

    Selecting only certain properties of your schema

    In addition to extending schemas, it is also possible to reduce them into smaller schemas. This comes in handy when you have a large Fluent Schema, and would like to re-use some of its properties.

    const S = require('fluent-json-schema')
    const userSchema = S.object()
      .prop('username', S.string())
      .prop('password', S.string())
      .prop('id', S.string().format('uuid'))
      .prop('createdAt', S.string().format('time'))
      .prop('updatedAt', S.string().format('time'))
    
    const loginSchema = userSchema.only(['username', 'password'])

    Detect Fluent Schema objects

    Every Fluent Schema object contains a boolean isFluentSchema. In this way, you can write your own utilities that understands the Fluent Schema API and improve the user experience of your tool.

    const S = require('fluent-json-schema')
    const schema = S.object().prop('foo', S.string()).prop('bar', S.number())
    console.log(schema.isFluentSchema) // true

    Documentation

    Acknowledgments

    Thanks to Matteo Collina for pushing me to implement this utility! 🙏

    Related projects

    Licence

    Licensed under MIT.

    Install

    npm i fluent-json-schema

    DownloadsWeekly Downloads

    6,056

    Version

    3.0.1

    License

    MIT

    Unpacked Size

    254 kB

    Total Files

    38

    Last publish

    Collaborators

    • aboutlo