question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to get jsonschema for only one class ?

See original GitHub issue

Hello,

In your exemple you give:

import { IsOptional, IsString, MaxLength } from 'class-validator'
import { validationMetadatasToSchemas } from 'class-validator-jsonschema'

class BlogPost {
  @IsString() id: string

  @IsOptional()
  @MaxLength(20, { each: true })
  tags: string[]
}

const schemas = validationMetadatasToSchemas()
console.log(schemas)

Unfortunatly I have too much classes using class-validator and I only want some. Is there a way to only get for given ones ?

Something like this:

import { IsOptional, IsString, MaxLength } from 'class-validator'
import { validationMetadatasToSchemas } from 'class-validator-jsonschema'

class BlogPost {
  @IsString() id: string

  @IsOptional()
  @MaxLength(20, { each: true })
  tags: string[]
}

const schema = validationClassToSchema(BlogPost) // or validationClassToSchemas([BlogPost])
console.log(schema)

Tried to create my own MetadataStorage with only the classes I want to be in but I don’t find any exemples on how to achieve that. Did you have ?

Actually I do:

const schemasToGet = [BlogPost.name];
const configSchema = validationMetadatasToSchemas();
for (const name of schemasToGet) {
  this._configSchema[name] = configSchema[name];
}

Thanks,

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:4
  • Comments:10 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
vadisticcommented, Feb 10, 2022

Just stumbled across this issue, my case is that my schema names across project are not by any means unique, and there is few completely separate domains.

Btw. sorry for spam / long samples.

My solution is to recursivelly inline schema objects instead of using refs with custom NESTED_VALIDATION constraint converter. But it could be easily adapted to dynamic definition.

import { Contructor } from 'type-fest'
// @ts-ignore
import { defaultMetadataStorage } from 'class-transformer/cjs/storage.js'
import { getMetadataStorage, ValidationTypes } from 'class-validator'
import { targetConstructorToSchema } from 'class-validator-jsonschema'
import { ISchemaConverters } from 'class-validator-jsonschema/build/defaultConverters'
import { IOptions } from 'class-validator-jsonschema/build/options'
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata'

export const classToJsonSchema = (clz: Constructor<any>) => {
  return targetConstructorToSchema(clz, options)
}

const additionalConverters: ISchemaConverters = {
  [ValidationTypes.NESTED_VALIDATION]: plainNestedConverter,
}

const options: Partial<IOptions> = {
  classTransformerMetadataStorage: defaultMetadataStorage,
  classValidatorMetadataStorage: getMetadataStorage(),
  additionalConverters,
}

/**
 * Explicitly inline nested schemas instead of using refs
 *
 * @see https://github.com/epiphone/class-validator-jsonschema/blob/766c02dd0de188ebeb697f3296982997249bffc9/src/defaultConverters.ts#L25
 */
function plainNestedConverter(meta: ValidationMetadata, options: IOptions) {
  if (typeof meta.target === 'function') {
    const typeMeta = options.classTransformerMetadataStorage
      ? options.classTransformerMetadataStorage.findTypeMetadata(meta.target, meta.propertyName)
      : null

    const childType = typeMeta
      ? typeMeta.typeFunction()
      : getPropType(meta.target.prototype, meta.propertyName)

    return targetToSchema(childType, options)
  }
}

function getPropType(target: object, property: string) {
  return Reflect.getMetadata('design:type', target, property)
}

function targetToSchema(type: any, options: IOptions): any | void {
  if (typeof type === 'function') {
    if (type.prototype === String.prototype || type.prototype === Symbol.prototype) {
      return { type: 'string' }
    } else if (type.prototype === Number.prototype) {
      return { type: 'number' }
    } else if (type.prototype === Boolean.prototype) {
      return { type: 'boolean' }
    }

    return classToJsonSchema(type)
  }
}

So class Options with NestedOptions

 class NestedOptions {
      @IsInt()
      @IsPositive()
      @Type(() => Number)
      int!: number

      @IsBoolean()
      @IsOptional()
      @Type(() => Boolean)
      bool?: boolean = false
    }

    class Options {
      @IsString()
      @Type(() => String)
      str!: string

      @IsArray()
      @IsString({ each: true })
      @Type(() => String)
      arr!: string[]

      @IsType(() => NestedOptions)
      nested!: NestedOptions
    }

Produces huge but valid schema

      {
        "properties": {
          "str": {
            "type": "string"
          },
          "arr": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "nested": {
            "properties": {
              "int": {
                "exclusiveMinimum": true,
                "minimum": 0,
                "type": "integer"
              },
              "bool": {
                "type": "boolean"
              }
            },
            "type": "object",
            "required": [
              "int"
            ]
          }
        },
        "type": "object",
        "required": [
          "str",
          "arr",
          "nested"
        ]
      }

// EDIT - solution with definitions object

import { Constructor } from 'type-fest'
// @ts-ignore
import { defaultMetadataStorage } from 'class-transformer/cjs/storage.js'
import { getMetadataStorage, IS_NEGATIVE, IS_POSITIVE, ValidationTypes } from 'class-validator'
import { targetConstructorToSchema } from 'class-validator-jsonschema'
import { ISchemaConverters } from 'class-validator-jsonschema/build/defaultConverters'
import { IOptions } from 'class-validator-jsonschema/build/options'
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata'

import { JSONSchema } from '../types/index.js'

export { JSONSchema as IsSchema } from 'class-validator-jsonschema'

/**
 * Build json-schema from `class-validator` & `class-tranformer` metadata.
 *
 * @see https://github.com/epiphone/class-validator-jsonschema
 */
export function classToJsonSchema(clz: Constructor<any>): JSONSchema {
  const options = { ...defaultOptions, definitions: {} }
  const schema = targetConstructorToSchema(clz, options) as any

  schema.definitions = options.definitions

  return schema
}

function nestedClassToJsonSchema(clz: Constructor<any>, options: Partial<Options>): JSONSchema {
  return targetConstructorToSchema(clz, options) as any
}

const additionalConverters: ISchemaConverters = {
  /**
   * Explicitly inline nested schemas instead of using refs
   *
   * @see https://github.com/epiphone/class-validator-jsonschema/blob/766c02dd0de188ebeb697f3296982997249bffc9/src/defaultConverters.ts#L25
   */
  [ValidationTypes.NESTED_VALIDATION]: (meta: ValidationMetadata, options: Options) => {
    if (typeof meta.target === 'function') {
      const typeMeta = options.classTransformerMetadataStorage
        ? options.classTransformerMetadataStorage.findTypeMetadata(meta.target, meta.propertyName)
        : null

      const childType = typeMeta
        ? typeMeta.typeFunction()
        : getPropType(meta.target.prototype, meta.propertyName)

      const schema = targetToSchema(childType, options)

      if (schema.$ref && !options.definitions[childType.name]) {
        options.definitions[childType.name] = nestedClassToJsonSchema(childType, options)
      }

      return schema
    }
  },
}

type Options = IOptions & {
  definitions: Record<string, JSONSchema>
}

const defaultOptions: Partial<Options> = {
  classTransformerMetadataStorage: defaultMetadataStorage,
  classValidatorMetadataStorage: getMetadataStorage(),
  additionalConverters,
}

function getPropType(target: object, property: string) {
  return Reflect.getMetadata('design:type', target, property)
}

function targetToSchema(type: any, options: IOptions): any | void {
  if (typeof type === 'function') {
    if (type.prototype === String.prototype || type.prototype === Symbol.prototype) {
      return { type: 'string' }
    } else if (type.prototype === Number.prototype) {
      return { type: 'number' }
    } else if (type.prototype === Boolean.prototype) {
      return { type: 'boolean' }
    }

    return { $ref: options.refPointerPrefix + type.name }
  }
}
1reaction
ThatOneAwkwardGuycommented, Jun 12, 2021

@scorsi

Just in case you’re still wondering about this, I managed to get it to work by making a new file that will hold my schema and doing something like this:

import 'reflect-metadata';
import 'es6-shim';

import { ClientUser } from '../schema/User/ClientUser';
import { OmitType } from '@nestjs/mapped-types';
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';

//Users
class ClientUserSchema extends OmitType(ClientUser, ['_id']) {}

console.log(ClientUserSchema);

const schemas = validationMetadatasToSchemas();
class ClientUserSchema extends OmitType(ClientUser, ['_id']) {}

Creates a new class that extends my initial class but omits the _id property, you can also pass an empty array and I believe it still works. This provides basically the same class to the validationMetadatasToSchemas and allows me to get a result.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Getting Started Step-By-Step | JSON Schema
To start a schema definition, let's begin with a basic JSON schema. We start with four properties called keywords which are expressed as...
Read more >
How do I require one field or another or (one of two others) ...
If one of the properties is * submitted with a truthy string value, then the other will * not be required to have...
Read more >
Introduction to JSON Schema in Java
A beginner's look at JSON Schema: a declarative language for validating the format and structure of a JSON Object.
Read more >
JSON Schema Serializer and Deserializer
Plug the KafkaJsonSchemaSerializer into KafkaProducer to send messages of JSON Schema type to Kafka. Assuming you have a Java class that is decorated...
Read more >
How to Validate Your JSON Using JSON Schema
A sample schema, like what we'd get from json.load() > ... In some cases, the value can be of only a specific type,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found