Security News
Introducing the Socket Python SDK
The initial version of the Socket Python SDK is now on PyPI, enabling developers to more easily interact with the Socket REST API in Python projects.
fluent-json-schema
Advanced tools
A fluent API to generate JSON schemas (draft-07) for Node.js and browser. Framework agnostic.
npm install fluent-json-schema --save
or
yarn add fluent-json-schema
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"]
}
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()
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
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'",
},
]
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"' } ]
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\'' } ]
Snippet:
user = { email: 'test@foo.com', password: 'password' }
valid = validate(user)
console.log({ valid })
Output:
{valid: true}
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)
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.
Select only properties you want to keep.
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'])
Or remove properties you dont want to keep.
const S = require('fluent-json-schema')
const personSchema = S.object()
.prop('name', S.string())
.prop('age', S.number())
.prop('id', S.string().format('uuid'))
.prop('createdAt', S.string().format('time'))
.prop('updatedAt', S.string().format('time'))
const bodySchema = personSchema.without(['createdAt', 'updatedAt'])
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
Thanks to Matteo Collina for pushing me to implement this utility! 🙏
Licensed under MIT.
FAQs
JSON Schema fluent API
The npm package fluent-json-schema receives a total of 17,376 weekly downloads. As such, fluent-json-schema popularity was classified as popular.
We found that fluent-json-schema demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 10 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The initial version of the Socket Python SDK is now on PyPI, enabling developers to more easily interact with the Socket REST API in Python projects.
Security News
Floating dependency ranges in npm can introduce instability and security risks into your project by allowing unverified or incompatible versions to be installed automatically, leading to unpredictable behavior and potential conflicts.
Security News
A new Rust RFC proposes "Trusted Publishing" for Crates.io, introducing short-lived access tokens via OIDC to improve security and reduce risks associated with long-lived API tokens.