Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 50 additions & 20 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,32 @@ module.exports = {
*/
function modelToJSONSchema(model, options) {
'use strict';

var schema = model.schema,
reserved = {
'_id': true,
'__v': true
},
result = {
$schema: 'http://json-schema.org/schema#',
title: model.modelName,
type: 'object',
properties: {},
required: schema.requiredPaths().filter(function (requiredProp) {
return requiredProp.indexOf('.') === -1;
})
};
var schema;
var result = {};
var reserved = {
'_id': true,
'__v': true
};
if (model.schema) {
schema = model.schema;
result = {
$schema: 'http://json-schema.org/schema#',
title: model.modelName,
type: 'object',
properties: {},
required: schema.requiredPaths().filter(function (requiredProp) {
return requiredProp.indexOf('.') === -1;
})
};
} else {
schema = model;
result = {
properties: {},
required: schema.requiredPaths().filter(function (requiredProp) {
return requiredProp.indexOf('.') === -1;
})
};
}

options = options || {};

Expand All @@ -55,7 +66,6 @@ function modelToJSONSchema(model, options) {

var frags = path.split('.'),
name = frags.pop();

var pathOptions = schema.path(path).options;

switch (pathOptions.type.name) {
Expand All @@ -82,9 +92,18 @@ function modelToJSONSchema(model, options) {
// typed array
if (Array.isArray(pathOptions.type)) {
property.type = 'array';
property.items = {
type: schema.path(path).casterConstructor.name.toLowerCase()
};
if (pathOptions.type[0].type && pathOptions.type[0].type.obj) {
property.items = modelToJSONSchema(pathOptions.type[0].type, options)
} else {
property.items = {
type: schema.path(path).casterConstructor.name.toLowerCase()
};
}
}

//typed object
if (pathOptions.type.obj) {
property = modelToJSONSchema(pathOptions.type, options)
}
}

Expand All @@ -96,7 +115,6 @@ function modelToJSONSchema(model, options) {
property.default = pathOptions.default;
}

//console.log(schema.path(path));
var properties = result.properties;

frags.forEach(function (frag, index) {
Expand All @@ -118,5 +136,17 @@ function modelToJSONSchema(model, options) {
}
});

for (var virtual in schema.virtuals) {
if (!reserved[virtual]) {
var property = {};

var pathGetters = schema.virtuals[`${virtual}`].getters;
property.type = (typeof pathGetters[0]()).toLowerCase()

var properties = result.properties;
properties[virtual] = property;
}
}

return result;
}
23 changes: 23 additions & 0 deletions lib/local-types/mongoose-jsonschema/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path="../../index.js" />
import * as mongoose from "mongoose";

/**
* @alias module:mongoose-jsonschema
*/
declare module MongooseJsonSchema {
/**
* @param {mongoose.Model<any>} model Mongoose model to be converted
* @param {any} options Options for customising model conversion
* @param {string[]|object} options.reserved - Model properties found in the array are not included in the schema or map of properties to be converted
* @param {boolean} options.reserved.property - Include/do not include model `property` into schema
* @returns {any} JSONSchema
*/
export function modelToJSONSchema(model: mongoose.Model<any>, options: any): any;
/**
* @param {mongoose.Model<any>} model Mongoose model to be converted
* @returns {any} JSONSchema
*/
export function modelToJSONSchema(model: mongoose.Model<any>): any;
}

export default MongooseJsonSchema;
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "mongoose-jsonschema",
"version": "0.3.0",
"version": "0.4.0",
"description": "Mongoose schema to JSON schema and back",
"main": "lib/index.js",
"typings": "lib/local-types/mongoose-jsonschema/index.d.ts",
"scripts": {
"test": "mocha",
"test": "ts-mocha -p tsconfig.json test/**/typegoose-test.ts",
"test-cov": "istanbul cover _mocha -- -R spec --check-leaks",
"test-travis": "istanbul cover _mocha --report lcovonly -- --reporter dot"
},
Expand All @@ -15,13 +16,18 @@
],
"author": "Vlad Stirbu <[email protected]>",
"license": "MIT",
"dependencies": {
},
"dependencies": {},
"devDependencies": {
"@typegoose/typegoose": "^7.4.5",
"@types/chai": "^4.2.14",
"@types/mocha": "^8.0.4",
"@types/mongoose": "^5.10.2",
"chai": "^3.5.0",
"express": "^4.14.0",
"mocha": "^3.1.0",
"mongoose": "^4.6.1"
"mongoose": "^5.9.14",
"ts-mocha": "^8.0.0",
"typescript": "^4.1.2"
},
"repository": {
"type": "git",
Expand Down
27 changes: 26 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/// <reference path="../typings/mocha/mocha.d.ts"/>
/// <reference path="../typings/mongoose/mongoose.d.ts"/>


var chai = require('chai'),
mongoose = require('mongoose'),

Expand All @@ -16,8 +17,9 @@ describe('modelToJSONSchema', function () {
expect(jsonSchema.properties.arrayProp.type).to.be.equal('array');

expect(jsonSchema.properties.arrayTypedProp.type).to.be.equal('array');
// updated mongoose which cause object to be mixed in this case
expect(jsonSchema.properties.arrayTypedProp.items).to.be.deep.equal({
type: 'object'
type: 'mixed'
});

expect(jsonSchema.properties.mixedProp.type).to.be.equal('object');
Expand Down Expand Up @@ -61,6 +63,29 @@ describe('modelToJSONSchema', function () {
expect(jsonSchema.properties.root.required).to.be.deep.equal(['nestedProp']);
});

it('should convert nested schema object', function () {
var jsonSchema = lib.modelToJSONSchema(mongoose.model('SchemaInSchema'));

expect(jsonSchema.properties.name).to.exist;
expect(jsonSchema.properties.address).to.exist;
expect(jsonSchema.required).to.be.deep.equal(['address', 'name']);
expect(jsonSchema.properties.address.properties.street_address).to.exist;
expect(jsonSchema.properties.address.properties.postal_code).to.exist;
expect(jsonSchema.properties.address.properties.locality).to.exist;
expect(jsonSchema.properties.address.properties.country).to.exist;
expect(jsonSchema.properties.address.required).to.be.deep.equal(['country', 'postal_code', 'locality', 'street_address']);
});

it('should convert virtual properties (such as functions..)', function () {
var jsonSchema = lib.modelToJSONSchema(mongoose.model('VirtualFunction'));

expect(jsonSchema.properties.stringProp).to.exist;
expect(jsonSchema.properties.stringProp.type).to.be.equal('string');

expect(jsonSchema.properties.numberProp).to.exist;
expect(jsonSchema.properties.numberProp.type).to.be.equal('number');
});

describe('options', function(){
describe('reserved', function(){
it('should filter out fields provided as an array', function(){
Expand Down
2 changes: 2 additions & 0 deletions test/mocha.opts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
--reporter list
--require test/support/node.js
./test
--recursive
20 changes: 20 additions & 0 deletions test/schemas/arrayNestedOfObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { buildSchema, getModelForClass, prop, ReturnModelType } from '@typegoose/typegoose';

class ArrayNestedOfObjects {
@prop({ type: () => [arrayTypedProp], _id: false })
arrayTypedProp?: arrayTypedProp[];
}

class arrayTypedProp {
@prop({ required: true })
stringProp!: String;
}

let ArrayNestedOfObjectsModel: ReturnModelType<
typeof ArrayNestedOfObjects,
{}
>;

ArrayNestedOfObjectsModel = getModelForClass(ArrayNestedOfObjects);

export {ArrayNestedOfObjectsModel};
39 changes: 39 additions & 0 deletions test/schemas/nestedSchemaObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
var mongoose = require('mongoose');

var addressSchema = new mongoose.Schema(
{
street_address: {
type: String,
required: true
},
locality: {
type: String,
required: true
},
postal_code: {
type: String,
required: true
},
country: {
type: String,
required: true
},
}
)

var schema = new mongoose.Schema(
{
name: {
type: String,
required: true
},
address: {
type: addressSchema,
required: true
}
}
);

var model = mongoose.model('SchemaInSchema', schema);

module.exports = model;
20 changes: 20 additions & 0 deletions test/schemas/virtualFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var mongoose = require('mongoose');

var schema = new mongoose.Schema({
});

schema
.virtual('stringProp')
.get(function () {
return 'string';
});
schema
.virtual('numberProp')
.get(function() {
return 0;
});


var model = mongoose.model('VirtualFunction', schema);

module.exports = model;
4 changes: 3 additions & 1 deletion test/support/node.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* global global */
require('../schemas/constraints');
require('../schemas/nested');
require('../schemas/types');
require('../schemas/types');
require('../schemas/nestedSchemaObject');
require('../schemas/virtualFunction');
17 changes: 17 additions & 0 deletions test/typegoose-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as chai from "chai";
import {ArrayNestedOfObjectsModel} from './schemas/arrayNestedOfObject';
const expect = chai.expect;
const lib = require('../');

describe('modelToJSONSchema', function () {

it('should convert objects nested in arrays', function() {
var jsonSchema = lib.modelToJSONSchema(ArrayNestedOfObjectsModel);

expect(jsonSchema.properties.arrayTypedProp).to.exist;
expect(jsonSchema.properties.arrayTypedProp.type).to.be.equal('array');

expect(jsonSchema.properties.arrayTypedProp.items.properties.stringProp).to.exist;
expect(jsonSchema.properties.arrayTypedProp.items.properties.stringProp.type).to.be.equal('string');
});
});
24 changes: 24 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"pretty": true,
"sourceMap": true,
"target": "es6",
"outDir": "./dist",
"baseUrl": "./",
"typeRoots": [
"node_modules/@types",
"lib/local-types"
],
"paths":{
"mongoose-jsonschema": ["./lib/local-types/mongoose-jsonschema"]
}
},
"exclude": [
"node_modules",
"lib/local-types/**"
]
}
Loading