Generating JSON Schema

You can conveniently generate JSON schema from Tapir schema, which can be derived from your Scala types. Use TapirSchemaToJsonSchema:

"com.softwaremill.sttp.tapir" %% "tapir-apispec-docs" % "1.10.5"

Schema generation can now be performed like in the following example:

import sttp.apispec.{Schema => ASchema}
import sttp.tapir._
import sttp.tapir.docs.apispec.schema._
import sttp.tapir.generic.auto._

  object Childhood {
    case class Child(age: Int, height: Option[Int])
  }
  case class Parent(innerChildField: Child, childDetails: Childhood.Child)
  case class Child(childName: String) // to illustrate unique name generation
  val tSchema = implicitly[Schema[Parent]]

  val jsonSchema: ASchema = TapirSchemaToJsonSchema(
    tSchema,
    markOptionsAsNullable = true,
    metaSchema = MetaSchemaDraft04 // default
    // schemaName = sttp.atpir.docs.apispec.defaultSchemaName // default
)

All the nested schemas will be referenced from the $defs element.

Serializing JSON Schema

In order to generate a JSON representation of the schema, you can use Circe. For example, with sttp jsonschema-circe module:

"com.softwaremill.sttp.apispec" %% "jsonschema-circe" % "..."

you will get a codec for sttp.apispec.Schema:

import io.circe.Printer
import io.circe.syntax._
import sttp.apispec.circe._
import sttp.apispec.{Schema => ASchema, SchemaType => ASchemaType}
import sttp.tapir._
import sttp.tapir.docs.apispec.schema._
import sttp.tapir.generic.auto._
import sttp.tapir.Schema.annotations.title

  object Childhood {
    @title("my child") case class Child(age: Int, height: Option[Int])
  }
  case class Parent(innerChildField: Child, childDetails: Childhood.Child)
  case class Child(childName: String)
  val tSchema = implicitly[Schema[Parent]]

  val jsonSchema: ASchema = TapirSchemaToJsonSchema(
    tSchema,
    markOptionsAsNullable = true)
  
  // JSON serialization
  val schemaAsJson = jsonSchema.asJson
  val schemaStr: String = Printer.spaces2.print(schemaAsJson.deepDropNullValues)

The title annotation of the object will be by default the name of the case class. You can customize it with @title annotation. You can also disable generation of default title fields by setting an option addTitleToDefs to false. This example will produce following String:

{
  "$schema" : "http://json-schema.org/draft-04/schema#",
  "required" : [
    "innerChildField",
    "childDetails"
  ],
  "type" : "object",
  "properties" : {
    "innerChildField" : {
      "$ref" : "#/$defs/Child"
    },
    "childDetails" : {
      "$ref" : "#/$defs/Child1"
    }
  },
  "$defs" : {
    "Child" : {
      "title" : "Child",
      "required" : [
        "childName"
      ],
      "type" : "object",
      "properties" : {
        "childName" : {
          "type" : "string"
        }
      }
    },
    "Child1" : {
      "title" : "my child",
      "required" : [
        "age"
      ],
      "type" : "object",
      "properties" : {
        "age" : {
          "type" : "integer",
          "format" : "int32"
        },
        "height" : {
          "type" : [
            "integer",
            "null"
          ],
          "format" : "int32"
        }
      }
    }
  }
}