All Projects → andyglow → Scala Jsonschema

andyglow / Scala Jsonschema

Licence: other
Scala JSON Schema

Programming Languages

scala
5932 projects

Projects that are alternatives of or similar to Scala Jsonschema

Movement
Movement is an easier, simpler way to explore and use NIEM. Want to join the Movement and contribute to it? Start here.
Stars: ✭ 19 (-72.06%)
Mutual labels:  json-schema
Schema
JSON Schema for GeoJSON
Stars: ✭ 39 (-42.65%)
Mutual labels:  json-schema
Jsonschema Key Compression
Compress json-data based on its json-schema while still having valid json
Stars: ✭ 59 (-13.24%)
Mutual labels:  json-schema
Spectral
A flexible JSON/YAML linter for creating automated style guides, with baked in support for OpenAPI v2 & v3.
Stars: ✭ 876 (+1188.24%)
Mutual labels:  json-schema
Brutusin Rpc
Self-describing JSON-RPC web services over HTTP, with automatic API description based on JSON-Schema
Stars: ✭ 36 (-47.06%)
Mutual labels:  json-schema
Uvicorn Gunicorn Fastapi Docker
Docker image with Uvicorn managed by Gunicorn for high-performance FastAPI web applications in Python 3.6 and above with performance auto-tuning. Optionally with Alpine Linux.
Stars: ✭ 1,014 (+1391.18%)
Mutual labels:  json-schema
Apispec
A pluggable API specification generator. Currently supports the OpenAPI Specification (f.k.a. the Swagger specification)..
Stars: ✭ 831 (+1122.06%)
Mutual labels:  json-schema
Json Schema Form Core
Core library
Stars: ✭ 61 (-10.29%)
Mutual labels:  json-schema
Analysispreservation.cern.ch
Source code for the CERN Analysis Preservation portal
Stars: ✭ 37 (-45.59%)
Mutual labels:  json-schema
Rest Layer
REST Layer, Go (golang) REST API framework
Stars: ✭ 1,068 (+1470.59%)
Mutual labels:  json-schema
Fastapi
FastAPI framework, high performance, easy to learn, fast to code, ready for production
Stars: ✭ 39,588 (+58117.65%)
Mutual labels:  json-schema
Jschema
JSON Schema implementation
Stars: ✭ 30 (-55.88%)
Mutual labels:  json-schema
Oakdex Pokedex
Ruby Gem and Node Package for comprehensive Generation 1-7 Pokedex data, including 809 Pokémon, uses JSON schemas to verify the data
Stars: ✭ 44 (-35.29%)
Mutual labels:  json-schema
Jsonschema2graphql
Convert JSON schema to GraphQL types
Stars: ✭ 24 (-64.71%)
Mutual labels:  json-schema
Autocomplete Json
Atom autocomplete for JSON files
Stars: ✭ 60 (-11.76%)
Mutual labels:  json-schema
Electron Crash Report Service
Aggregate crash reports for Electron apps
Stars: ✭ 17 (-75%)
Mutual labels:  json-schema
Ncform
🍻 ncform, a very nice configuration generation way to develop forms ( vue, json-schema, form, generator )
Stars: ✭ 1,009 (+1383.82%)
Mutual labels:  json-schema
Schemasafe
A reasonably safe JSON Schema validator with draft-04/06/07/2019-09 support.
Stars: ✭ 67 (-1.47%)
Mutual labels:  json-schema
Laravel Json Schema Assertions
JSON Schema assertions for the Laravel framework
Stars: ✭ 61 (-10.29%)
Mutual labels:  json-schema
Univalue
High performance RAII C++ JSON library and universal value object class
Stars: ✭ 46 (-32.35%)
Mutual labels:  json-schema

Scala JSON Schema

Build Status codecov mvn: 2.11 mvn: 2.12 mvn: 2.13

SBT dependencies:

Main module:

libraryDependencies += "com.github.andyglow" %% "scala-jsonschema" % <version> // <-- required

Other libraries:

libraryDependencies ++= Seq(
  "com.github.andyglow" %% "scala-jsonschema-core" % <version>,              // <-- transitive
  "com.github.andyglow" %% "scala-jsonschema-macros" % <version> % Provided, // <-- transitive
  // json bridge. pick one
  "com.github.andyglow" %% "scala-jsonschema-play-json" % <version>,         // <-- optional
  "com.github.andyglow" %% "scala-jsonschema-spray-json" % <version>,        // <-- optional
  "com.github.andyglow" %% "scala-jsonschema-circe-json" % <version>,        // <-- optional
  "com.github.andyglow" %% "scala-jsonschema-json4s-json" % <version>,       // <-- optional
  "com.github.andyglow" %% "scala-jsonschema-ujson" % <version>,             // <-- optional
  // joda-time support
  "com.github.andyglow" %% "scala-jsonschema-joda-time" % <version>,         // <-- optional
  // cats support
  "com.github.andyglow" %% "scala-jsonschema-cats" % <version>,              // <-- optional
  // refined support
  "com.github.andyglow" %% "scala-jsonschema-refined" % <version>,           // <-- optional
  // enumeratum support
  "com.github.andyglow" %% "scala-jsonschema-enumeratum" % <version>,        // <-- optional
  // zero-dependency json and jsonschema parser
  "com.github.andyglow" %% "scala-jsonschema-parser" % <version>             // <-- optional
)

Generate JSON Schema from Scala classes

The goal of this library is to make JSON Schema generation done the way all popular JSON reading/writing libraries do. Inspired by Coursera Autoschema but uses Scala Macros instead of Java Reflection.

Features

  • Supports Json Schema draft-04, draft-06, draft-07
  • Supports case classes
  • Supports value classes
  • Supports sealed trait enums
  • Supports sealed trait case classes
  • Supports recursive types
  • Supports scala.Enumeration
  • Treats scaal.Option as optional fields
  • As well as treats fields with default values as optional
  • Any Iterable is treated as array
  • Pluggable Joda-Time Support
  • Pluggable Cats Support
  • Pluggable Refined Support
  • Pluggable Enumeratum Support
  • Supports generic data types

Types supported out of the box

  • Boolean
  • Numeric
    • Short
    • Int
    • Char
    • Double
    • Float
    • Long
    • BigInt
    • BigDecimal
  • String
  • Date Time
    • java.util.Date
    • java.sql.Timestamp
    • java.time.Instant
    • java.time.LocalDateTime
    • java.sql.Date
    • java.time.LocalDate
    • java.sql.Time
    • java.time.LocalTime
  • with JodaTime module imported
    • org.joda.time.Instant
    • org.joda.time.DateTime
    • org.joda.time.LocalDateTime
    • org.joda.time.LocalDate
    • org.joda.time.LocalTime
  • with Cats module imported
    • cats.data.NonEmptyList
    • cats.data.NonEmptyVector
    • cats.data.NonEmptySet
    • cats.data.NonEmptyChain
    • cats.data.NonEmptyMap
    • cats.data.NonEmptyStream (for scala 2.11, 2.12)
    • cats.data.NonEmptyLazyList (for scala 2.13)
    • cats.data.OneAnd
  • with Refined module imported you can refine original types with these
    • boolean
      • eu.timepit.refined.boolean.And
      • eu.timepit.refined.boolean.Or
      • eu.timepit.refined.boolean.Not
    • string
      • eu.timepit.refined.collection.Size
      • eu.timepit.refined.collection.MinSize
      • eu.timepit.refined.collection.MaxSize
      • eu.timepit.refined.collection.Empty
      • eu.timepit.refined.collection.NonEmpty
      • eu.timepit.refined.string.Uuid
      • eu.timepit.refined.string.Uri
      • eu.timepit.refined.string.Url
      • eu.timepit.refined.string.IPv4
      • eu.timepit.refined.string.IPv6
      • eu.timepit.refined.string.Xml
      • eu.timepit.refined.string.StartsWith
      • eu.timepit.refined.string.EndsWith
      • eu.timepit.refined.string.MatchesRegex
      • eu.timepit.refined.string.Trimmed
    • number
      • eu.timepit.refined.numeric.Positive
      • eu.timepit.refined.numeric.Negative
      • eu.timepit.refined.numeric.NonPositive
      • eu.timepit.refined.numeric.NonNegative
      • eu.timepit.refined.numeric.Greather
      • eu.timepit.refined.numeric.Less
      • eu.timepit.refined.numeric.GreaterEqual
      • eu.timepit.refined.numeric.LessEqual
      • eu.timepit.refined.numeric.Divisable
    • collection
      • eu.timepit.refined.collection.Size
      • eu.timepit.refined.collection.MinSize
      • eu.timepit.refined.collection.MaxSize
      • eu.timepit.refined.collection.Empty
      • eu.timepit.refined.collection.NonEmpty
  • with Enumeratum module enabled
    • enums based on EnumEntry/Enum
    • enums based on ValueEnumEntry/ValueEnum
  • Misc
    • java.util.UUID
    • java.net.URL
    • java.net.URI
  • Collections
    • String Map (eg. Map[String, T])
    • Int Map (eg. Map[Int, T])
    • Iterable[T]
  • Sealed Trait hierarchy of case objects (Enums)
  • Case Classes
    • default value
  • Sealed Trait hierarchy of case classes
  • Value Classes

Example

Suppose you have defined this data structures

sealed trait Gender

object Gender {

    case object Male extends Gender

    case object Female extends Gender
}

case class Company(name: String)

case class Car(name: String, manufacturer: Company)

case class Person(
    firstName: String,
    middleName: Option[String],
    lastName: String,
    gender: Gender,
    birthDay: java.time.LocalDateTime,
    company: Company,
    cars: Seq[Car])

Now you have several ways to specify your schema.

In-Lined

In simple words in-lined mode means you will have no definitions. Type you want to use as source for schema will be represented in json schema without reusable data blocks.

import json._

val personSchema: json.Schema[Person] = Json.schema[Person]

As result you will receive this:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "middleName": {
      "type": "string"
    },
    "cars": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "manufacturer": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "name": {
                "type": "string"
              }
            },
            "required": [
              "name"
            ]
          }
        },
        "required": [
          "name",
          "manufacturer"
        ]
      }
    },
    "company": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string"
        }
      },
      "required": [
        "name"
      ]
    },
    "lastName": {
      "type": "string"
    },
    "firstName": {
      "type": "string"
    },
    "birthDay": {
      "type": "string",
      "format": "date-time"
    },
    "gender": {
      "type": "string",
      "enum": [
        "Male",
        "Female"
      ]
    }
  },
  "required": [
    "company",
    "lastName",
    "birthDay",
    "gender",
    "firstName",
    "cars"
  ]
}

Regular

Schema generated in Regular mode will contain so many definitions so many separated definitions you provide. Lets take a look at example code:

import json._

implicit val genderSchema: json.Schema[Gender] = Json.schema[Gender]

implicit val companySchema: json.Schema[Company] = Json.schema[Company]

implicit val carSchema: json.Schema[Car] = Json.schema[Car]

implicit val personSchema: json.Schema[Person] = Json.schema[Person]

Here we defined, besides Person schema, gender, company and car schemas. The result will be looking this way then.

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "middleName": {
      "type": "string"
    },
    "cars": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/com.github.andyglow.jsonschema.ExampleMsg.Car"
      }
    },
    "company": {
      "$ref": "#/definitions/com.github.andyglow.jsonschema.ExampleMsg.Company"
    },
    "lastName": {
      "type": "string"
    },
    "firstName": {
      "type": "string"
    },
    "birthDay": {
      "type": "string",
      "format": "date-time"
    },
    "gender": {
      "$ref": "#/definitions/com.github.andyglow.jsonschema.ExampleMsg.Gender"
    }
  },
  "required": [
    "company",
    "lastName",
    "birthDay",
    "gender",
    "firstName",
    "cars"
  ],
  "definitions": {
    "com.github.andyglow.jsonschema.ExampleMsg.Company": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string"
        }
      },
      "required": [
        "name"
      ]
    },
    "com.github.andyglow.jsonschema.ExampleMsg.Car": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string"
        },
        "manufacturer": {
          "$ref": "#/definitions/com.github.andyglow.jsonschema.ExampleMsg.Company"
        }
      },
      "required": [
        "name",
        "manufacturer"
      ]
    },
    "com.github.andyglow.jsonschema.ExampleMsg.Gender": {
      "type": "string",
      "enum": [
        "Male",
        "Female"
      ]
    }
  }
}

Definitions/References

There are couple of ways to specify reference of schema.

  1. It could be generated from type name (including type args)
  2. You can do it yourself. It is useful when you want to provide couple of schemas with same type but with different validation rules.

So originally you use

import json._

implicit val someStrSchema: json.Schema[String] = Json.schema[String]

implicit val someArrSchema: json.Schema[Array[String]] = Json.schema[Array[String]]

println(JsonFormatter.format(AsValue.schema(someArrSchema)))
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "array",
  "items": {
    "$ref": "#/definitions/java.lang.String"
  },
  "definitions": {
    "java.lang.String": {
      "type": "string"
    }
  }
}

See that java.lang.String?

To use custom name, just apply it.

import json._

implicit val someStrSchema: json.Schema[String] = Json.schema[String]("my-lovely-string")

implicit val someArrSchema: json.Schema[Array[String]] = Json.schema[Array[String]]

println(JsonFormatter.format(AsValue.schema(someArrSchema, json.schema.Version.Draft04())))
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "array",
  "items": {
    "$ref": "#/definitions/my-lovely-string"
  },
  "definitions": {
    "my-lovely-string": {
      "type": "string"
    }
  }
}

There is, though, one circumstance that will make you think twice defining implicit val someStrSchema: json.Schema[String] = Json.schema[String] as it will influence all string fields or components of your schema. Say you want to use simple string along with validated string for ID representation. As the library operates at compile time level it completely rely on type information and thus it limits us to only one solution: specify special types as types.

Use Value Classes.

case class UserId(value: String) extends AnyVal

case class User(id: UserId, name: String)

Then you can do

import json._

implicit val userIdSchema: json.Schema[UserId] = Json.schema[UserId]("userId")

implicit val userSchema: json.Schema[User] = Json.schema[User]

println(JsonFormatter.format(AsValue.schema(someArrSchema)))

and expect

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "id": {
      "$ref": "#/definitions/userId"
    },
    "name": {
      "type": "string"
    },
    "required": [
      "id",
      "name"
    ],
    "definitions": {
      "userId": {
        "type": "string"
      }
    }
  }
}

Validation

It is also possible to add specific validation rules to our schemas.

Available validations:

  • multipleOf
  • maximum
  • minimum
  • exclusiveMaximum
  • exclusiveMinimum
  • maxLength
  • minLength
  • pattern
  • maxItems
  • minItems
  • uniqueItems
  • maxProperties
  • minProperties

Example

import json._
import json.Validation._

implicit val vb = ValidationBound.mk[UserId, String]

implicit val userIdSchema: json.Schema[UserId] = Json.schema[UserId]("userId") withValidation (
  `pattern` := "[a-f\\d]{16}"
)

Definition will look then like

{
  "userId": {
    "type": "string",
    "pattern": "[a-f\\d]{16}"
  }
}

Free objects

Sometimes you need to include some more relaxed structure like the json itself into your models. In such cases you want your final schema would contain something like this:

{
  "type": "object",
  "additionalProperties": true
}

In order to get this, you can use Schema.object.Free. Like in this Play-Json based example:

import play.api.libs.json._

// model
case class Payload(id: String, name: String, metadata: JsObject)

// metadata schema
implicit val metaSchema: json.Schema[JsObject] = json.Schema.`object`.Free[JsObject]()

// or alternatively define a metadata Predef in case you need this to not go to definition section of json-schema
// implicit val metaPredef: json.schema.Predef[JsObject] = json.schema.Predef(json.Schema.`object`.Free[JsObject]())

// person schema
val personSchema: json.Schema[Person] = Json.schema[Person]

Joda Time

Joda Time Support allows you to use joda-time classes within your models. Here is an example.

import com.github.andyglow.jsonschema.JodaTimeSupport._
import org.joda.time._

case class Event(id: String, timestamp: Instant)

val eventSchema: Schema[Event] = Json.schema[Event]

println(JsonFormatter.format(AsValue.schema(eventSchema)))

results in

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "id": {
      "type": "string"
    },
    "timestamp": {
      "$ref": "#/definitions/org.joda.time.Instant"
    }
  },
  "required": [
    "id",
    "timestamp"
  ],
  "definitions": {
    "org.joda.time.Instant": {
      "type": "string",
      "format": "date-time"
    }
  }
}

Json Libraries

The library uses its own Json model com.github.andyglow.json.Value to represent Json Schema as JSON document. But project contains additionally several modules which could connect it with library of your choice.

Currently supported:

  • Play Json
  • Spray Json
  • Circe
  • Json4s
  • uJson

Example usage: Play

import com.github.andyglow.jsonschema.AsPlay._
import json.schema.Version._
import play.api.libs.json._

case class Foo(name: String)

val fooSchema: JsValue = Json.schema[Foo].asPlay(Draft04())

Example usage: Spray

import com.github.andyglow.jsonschema.AsSpray._
import json.schema.Version._
import spray.json._

case class Foo(name: String)

val fooSchema: JsValue = Json.schema[Foo].asSpray(Draft04())

Example usage: Circe

import com.github.andyglow.jsonschema.AsCirce._
import json.schema.Version._
import io.circe._

case class Foo(name: String)

val fooSchema: Json = Json.schema[Foo].asCirce(Draft04())

Example usage: Json4s

import com.github.andyglow.jsonschema.AsJson4s._
import json.schema.Version._
import org.json4s.JsonAST._

case class Foo(name: String)

val fooSchema: JValue = Json.schema[Foo].asJson4s(Draft04())

Example usage: uJson

import com.github.andyglow.jsonschema.AsU._
import json.schema.Version._

case class Foo(name: String)

val fooSchema: ujson.Value = Json.schema[Foo].asU(Draft04())

Documentation

3 ways to maintain documented models are supported.

  1. Annotations
  2. Config
  3. Scaladoc

Annotations

Scala-JsonSchema specifies 2 annotations that can help you specify a model @title and @description as well as fields @descriptions.

Example:

import json._
import json.schema._

@title("A Title")
@description("My perfect class")
case class Model(
    @description("A Param") a: String,
    @description("B Param") b: Int)

val schema = Json.objectSchema[Model]()

this, being translated into json, gets you

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "My perfect class",
  "title": "A Title",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "a": {
      "type": "string",
      "description": "A Param"
    },
    "b": {
      "type": "integer",
      "description": "B Param"
    }
  },
  "required": [
    "a",
    "b"
  ]
}

Config

Another approach that you can use to keep your models concise, but documented is to provide documentation separately. As config.

Here is an example:

import json._

case class Model(a: String, b: Int)

val schema = Json.objectSchema[Model](
  "a" -> "A Param",
  "b" -> "B Param"
) .withDescription("My perfect class")
  .withTitle("A Title")

this, being translated into json, gets you the same effect as annotation based approach

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "My perfect class",
  "title": "A Title",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "a": {
      "type": "string",
      "description": "A Param"
    },
    "b": {
      "type": "integer",
      "description": "B Param"
    }
  },
  "required": [
    "a",
    "b"
  ]
}

This approach also nicely fits when models are specified in separate module or external library.

Scaladoc

Also it is possible to infer descriptions from scaladoc. This allows to reuse scaladoc that you might want to have anyways. This approach has it's own drawbacks, though.

  • model classes must reside in the same module with schemas
  • it requires non-incremental build or full-rebuild to take effect

Example:

import json._

/** My perfect class
 * 
 * @param a A Param
 * @param b B Param
 */
case class Model(a: String, b: Int)
val schema = Json.objectSchema[Model]()

this, being translated into json, gets you

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "My perfect class",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "a": {
      "type": "string",
      "description": "A Param"
    },
    "b": {
      "type": "integer",
      "description": "B Param"
    }
  },
  "required": [
    "a",
    "b"
  ]
}

One little difference comparing to previous approaches is that this way you can't have title specified.

Combined approach

All these 3 techniques can be used all together. The only thing you need to have in mind if going this way is that to extract different type of label Scala-JsonSchema will check certain sources in certain order.

Element Order
case class title Config -> Annotation -> Scaladoc
case class description Config -> Annotation -> Scaladoc
case class field description Config -> Annotation -> Scaladoc

Annotations

Annotation Scope Description
@readOnly Field Adds "readOnly": true to property definition
@writeOnly Field Adds "writeOnly": true to property definition
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].