Skip to content

Commit

Permalink
Basic Schema conversion works :)
Browse files Browse the repository at this point in the history
  • Loading branch information
kristianmandrup committed Sep 4, 2018
0 parents commit e8915e7
Show file tree
Hide file tree
Showing 16 changed files with 4,099 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
*.log
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
115 changes: 115 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# JSON Schema to Yup schema

Simple JSON Schema to Yup Schema conversion

[JSON Schema primer](https://support.riverbed.com/apis/steelscript/reschema/jsonschema.html)

Supports the most commonly used JSON Schema layout.
Also supports some extra convenience schema properties that make it more "smooth" to define validation requirements declaratively (see below).

Note that is you use these extra properties, the JSON Schema is no longer valid if used in a context where JSON Schema validation is performed.

We will likely later support normalization and de-normalization.

```js
const schema = {
$schema: "http://json-schema.org/draft-07/schema#",
$id: "http://example.com/person.schema.json",
title: "Person",
description: "A person",
type: "object",
properties: {
name: {
description: "Name of the person",
type: "string"
},
age: {
description: "Age of person",
type: "number",
exclusiveMinimum: 0,
required: true
}
},
required: ["name"]
};

const json2yup = require("json-schema-to-yup");

const yupSchema = json2yup(json);
// console.dir(schema)
const valid = await yupSchema.isValid({
name: "jimmy",
age: 24
});

console.log({
valid
});
// => {valid: true}
```

This would generate the following Yup validation schema:

```js
const schema = yup.object().shape({
name: yup.string().required(),
age: yup
.number()
.required()
.positive()
});
```

Note the `"required": true` for the `age` property (not natively supported by JSON schema).

## Complex example

Here a more complete example of the variations currently possible

```json
{
"title": "Person",
"description": "A person",
"type": "object",
"properties": {
"name": {
"description": "Name of the person",
"type": "string",
"required": true,
"matches": "[a-zA-Z- ]+",
"mix": 3,
"maxLength": 40
},
"age": {
"description": "Age of person",
"type": "integer",
"min": 0,
"max": 130,
"required": false
},
"birthday": {
"type": "date",
"min": "1-1-1900",
"maxDate": "1-1-2015"
},
"smoker": {
"type": "boolean"
},
"mother": {
"type": "object",
"properties": {}
},
"siblings": {
"type": "array",
"items": {
"type": "object",
"properties": {}
}
}
}
}
```

## License

MIT
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./src')
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "json-schema-to-yup",
"version": "1.0.0",
"main": "src/index.js",
"license": "MIT",
"dependencies": {
"yup": "^0.26.3"
},
"devDependencies": {
"jest": "^23.5.0"
}
}
119 changes: 119 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const yup = require('yup');
const {
toYupString,
toYupNumber,
toYupBoolean,
toYupArray,
toYupObject,
toYupMixed,
toYupDate
} = require('./types')

module.exports = (schema, config = {}) => {
let {
type,
properties
} = schema
if (isObject(type)) {
if (properties) {
properties = normalizeRequired(schema)
const shapeConfig = propsToShape(properties, config)
return yup.object().shape(shapeConfig)
}
}
throw new Error('invalid schema')
}

function isObject(type) {
return type && type === 'object'
}

function normalizeRequired(schema) {
const {
properties,
required
} = schema
return Object.keys(properties).reduce((acc, key) => {
const value = properties[key]
const isRequired = required.indexOf(key) >= 0
value.required = value.required || isRequired
acc[key] = value
return acc
}, {})
}

function propsToShape(properties, config = {}) {
return Object.keys(properties).reduce((acc, key) => {
const value = properties[key]
acc[key] = propToYupSchemaEntry(key, value, config)
return acc
}, {})
}

function propToYupSchemaEntry(key, value, config = {}) {
return new YupSchemaEntry(key, value, config).toEntry()
}

class YupSchemaEntry {
constructor(key, value, config) {
this.key = key
this.value = value
this.config = config
this.type = value.type
}

toEntry() {
const typeSchema = this.typeSchema()
return typeSchema
}

typeSchema() {
if (this.type) {
return this.string ||
this.number ||
this.boolean ||
this.array ||
this.object ||
this.date ||
this.mixed
}
return this.mixed
}

get obj() {
return {
key: this.key,
value: this.value,
type: this.type,
config: this.config
}
}

get string() {
return toYupString(this.obj)
}

get number() {
return toYupNumber(this.obj)
}

get boolean() {
return toYupBoolean(this.obj)
}

get array() {
return toYupArray(this.obj)
}

get object() {
return toYupObject(this.obj)
}

get date() {
return toYupDate(this.obj)
}

get mixed() {
return toYupMixed(this.obj)
}
}
68 changes: 68 additions & 0 deletions src/types/array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// See: http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.4

const {
YupMixed
} = require('./mixed')

function isBoolean(type) {
return type === 'array'
}

function toYupArray(obj) {
return isArray(obj.type) && YupArray.create(obj).yupped()
}

class YupArray extends YupMixed {
constructor(obj) {
super(obj)
this.type = 'array'
this.base = this.yup[this.type]()
}

convert() {
this.items().maxItems().minItems().uniqueItems().contains().additionalItems()
super.convert()
return this
}

items() {
return this
}

additionalItems() {
return this
}

maxItems() {
const {
maxItems,
max
} = this.value
const $max = maxItems || max
$max && this.base.min($max)
return this
}

minItems() {
const {
minItems,
min
} = this.value
const $min = maxItems || min
$min && this.base.min($min)
return this
}

uniqueItems() {
return this
}

contains() {
return this
}
}

module.exports = {
toYupArray,
YupArray
}
28 changes: 28 additions & 0 deletions src/types/boolean.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const {
YupMixed
} = require('./mixed')

function isBoolean(type) {
return type === 'boolean'
}

function toYupBoolean(obj) {
return isBoolean(obj.type) && YupBoolean.create(obj).yupped()
}

class YupBoolean extends YupMixed {
constructor(obj) {
super(obj)
this.type = 'boolean'
this.base = this.yup[this.type]()
}

static create(obj) {
return new YupBoolean(obj)
}
}

module.exports = {
toYupBoolean,
YupBoolean
}
Loading

0 comments on commit e8915e7

Please sign in to comment.