Code Monkey home page Code Monkey logo

scala-jsonschema's Introduction

Scala JSON Schema

Support ukraine Build codecov Version

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, draft-09, draft-12
  • Supports case classes
  • Supports value classes
  • Supports sealed trait enums
  • Supports sealed trait case classes
  • Supports recursive types
  • Supports scala.Enumeration
  • Treats scala.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 is a 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].toDefinition("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].toDefinition("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].toDefinition("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]())

// payload schema
val payloadSchema: json.Schema[Payload] = Json.schema[Payload]

Also, there is API to make object definition Free (and vice versa, a Free definition Strict)

case class Person(name: String, age: Int)
val personSchema = Json.objectSchema[Person]
val freePersonSchema = personSchema.free
val strictPersonSchema = freePersonSchema.strict

strictPersonSchema == personSchema // equal

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"
    }
  }
}

Cats

In order to enable integration with cats we not only add it to dependencies, we also need tp import the integration package.

import com.github.andyglow.jsonschema.CatsSupport._

// TODO: provide examples

Refined

For Refined types to get described accordingly we, besides adding integration to dependency list, need to import the integration package.

import com.github.andyglow.jsonschema.RefinedSupport._

// TODO: provide examples

Enumeratum

To stitch Enumeratum support in we need to, add correcponding integration to dependencies, as well as import the integration package.

import com.github.andyglow.jsonschema.EnumeratumSupport._

// TODO: provide examples

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())

Enumerations

A few words about enumeration support. Most of the time enumerations are enumerations, we don't need to know anything else except allowed values, that's it. But.. sometimes we need something more. Sometimes we need the specified values to show up some extra information. Some titles, descriptions, etc. json-schema doesn't support this, unfortunately. But we can work around this. We can make macro to generate oneof(const1, const2, const3, ...) instead of enum. For that you need to provide a special flag.

implicit val jsonSchemaFlags: Flag with Flag.EnumsAsOneOf = null

this should show up in implicit scope of the macro.

Example.. Say we have a Gender enum specified like this

sealed trait Gender
object Gender {
    case object Male extends Gender
    case object Female extends Gender
}

Usually Json.schema[Gender] returns something like this

{
  "type": "string",
  "enum": [
    "Male",
    "Female"
  ]
}

But after the flag added, what we have is

{
  "oneOf": [
    { "const": "Male" },
    { "const": "Female" }
  ]
}

With this said, we can add some titles and descriptions into our models. For example this model definition, with EnumsAsOneOf flag enabled

  sealed trait Gender
  object Gender {
    @title("The Male") case object Male extends Gender
    /** The Female
      */
    case object Female extends Gender
  }

will produce schema such as

{
  "oneOf": [
    {
      "title": "The Male",
      "const": "Male"
    },
    {
      "description": "The Female",
      "const": "Female"
    }
  ]
}

For better explanation on how to apply documentation tags to the model please refer to the next chapter.

Documentation

By documentation, we mean extra information that can be carried along with the schema in order to improve its clarity. This all basically is about support of 2 fields: title, description. There are 3 places where these fields may take a place.

  • root model level
  • definition level
  • one-of / all-of / any-of level

We have 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

scala-jsonschema's People

Contributors

andyglow avatar aprilatprotenus avatar justcoon avatar marcoist avatar michellemay avatar novakov-alexey avatar oker1 avatar scala-steward avatar xuwei-k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scala-jsonschema's Issues

Support for untyped or "metadata" field

I have a case class which is defined similar to the following:

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

where JsObject refers to the play-json class.

The field isn't have to be a JsObject necessarily but really is just intended to provide a place where a user can specify arbitrary untyped json.

I tried changing this to Object and tried to imagine how I could represent this as a Map[String, T] of some sort but wasn't able to come up with anything.

I read through docs and wandered through the codebase looking for some indication of how I might achieve this. Is this doable today?

Schema is not supported for sealed trait defined inside object

Please find the below snippet, where I faced the issue. Schema conversion failed for this:

sealed trait TemplateLayoutType {

  def asString: String 

  override def toString: String = asString

}


object TemplateLayoutType {

  case object XLayout extends TemplateLayoutType {

    override val asString = "X_LAYOUT"

  }

  case object YLayout extends TemplateLayoutType {

    override val asString = "Y_LAYOUT"

  }

  case object PVLayout extends TemplateLayoutType {

    override val asString = "PV_LAYOUT"

  }
  sealed trait TemplateLayoutType1 extends TemplateLayoutType {

    def asString: String

    override def toString: String = asString

  }

  object TemplateLayoutType1 {

    case object XLayout1 extends TemplateLayoutType1 {

      override val asString = "X_LAYOUT1"

    }

    case object YLayout1 extends TemplateLayoutType1 {

      override val asString = "Y_LAYOUT1"

    }

    case object PVLayout1 extends TemplateLayoutType1 {

      override val asString = "PV_LAYOUT1"

    }

  }

}

Please let me know if there is any solution for this.

Schema derivation fails for optional complex types with a default in 3.0

I have the following ammonite script:

import $ivy.`com.github.andyglow:scala-jsonschema-play-json_2.13:0.3.0`
import json._
case class Bar(b: Int)
implicit val barSchema = Json.schema[Bar]
case class Foo(bar: Option[Bar])
Json.schema[Foo] // succeeds
case class Foo(bar: Option[Bar] = Some(Bar(1)))
Json.schema[Foo] // fails

It prints out:

cmd7.sc:1: Can't infer a json value for bar
val res7 = Json.schema[Foo] // fails
^
Compilation Failed

Moreover if you adjust the imported version to "0.2.1" it succeeds.

I think I've tracked this to changes in the code that prints "can't infer a json value" but I'm not understanding the complexities there quite enough to try to figure out a workaround. It seems like a regression bug to me.

schema for Char is not supported

I try to upgrade from 0.2.2 to test #18 but my code doesn't compile with version 0.2.3+

I get the error schema for Char is not supported

I use it in a case class MyCaseClass(val value: Char) extends AnyVal but even if I do val mySchema: Schema[Char] = Json.schema[Char] I get the same error.

I use the following dependencies and have tried version 0.2.3 to 0.2.7-M1

		libraryDependencies += "com.github.andyglow" %% "scala-jsonschema-core" % "0.2.7-M1",
		libraryDependencies += "com.github.andyglow" %% "scala-jsonschema" % "0.2.7-M1",
		libraryDependencies += ("com.github.andyglow" %% "scala-jsonschema-circe-json" % "0.2.7-M1")

Trying other primitives than Char works eg Byte and Int.

According to the documentation Char is supported out of the box. Is this a bug or removed feature?

Add support for custom patternProperties for keys on Schema[Map[String, T]]

I would like to restrict the keyspace of Map[String, _] keys to help guarantee interoperability with other systems (e.g. NoSQL). However, currently the "patternProperties" is fixed with the pattern "^.*$". It would be nice to be able to specify the pattern(s) for keys similarly to how is done with Schema.string.

can't infer schema for optionable field with default None

Hi!

case class Boo(a: Int)
case class Foo(n: Int, boo: Option[Boo]=None)
Json.schema[Foo].asCirce(Draft07("aaa"))

compilation fails with Can't infer a json value for 'boo'.

But without None compiles OK.

case class Boo(a: Int)
case class Foo(n: Int, boo: Option[Boo])
Json.schema[Foo].asCirce(Draft07("aaa"))

Schema not compiling for a value class with Integer value

I try to create a schema for

case class PixelConfiguration(
                               name: String,
                               comments: List[String] = List(),
                               pixel: Option[Pixel] = None,
                               publisherId: Set[PublisherId] = Set(),
                               forwardPercent: Option[Int] = None
                             ) extends Model with Validates[PixelConfiguration]

where

case class PublisherId(val value: Int) extends AnyVal with IntField with Validates[PublisherId] 

with

val schemaJson: Json = Json.schema[List[PixelConfiguration]].asCirce(Version.Draft07(id = s"http://example.com/pixel-config-$version.json$timestamp"))

but get a type mismatch:

[error]  found   : json.Schema.integer.type
[error]  required: json.Schema[com.liveintent.dwh.common.fields.PublisherId]
[error]     val schemaJson: Json = Json.schema[List[PixelConfiguration]].asCirce(Version.Draft07(id = s"http://example.com/pixel-config-$version.json$timestamp"))

I have tried to follow the answers you gave in #19 and #22 but can't convince to Json.schema to treat as a list of PublisherId instead of Integers.

How do I force/workaround this?

[QUESTION] Example of overriding type behavior with implicits?

@andyglow Hi I was wondering if you could give an example of overriding types behavior. For example:

case class AnyThing(value:Any)
and when I do Json.schema[AnyThing].asCirce() it says

Error: schema for Any is not supported, 

I've tried making giving an implicit conversion of Any to a string value but it doesn't seem to work. Should I just try to implicitly convert AnyThing into an object with supported types? Im trying to avoid rewriting very complex objects with valid or omitted attributes.

I get the feeling this can be done because of this Implicit.getOrElse(tpe, genTree) in SchemaMacro.scala. I just can't figure it out or find an example.

Thank you!

Request for new use case

Hi, thanks for writing this software.

There is one use case that would be really wonderful if it was supported.

Basically, my project uses Circe to serialize an object of type FiniteDuration as a string. So for serialization, 10.seconds is "10s" and 10.milliseconds is "10ms". For deserialization, the deserializer reads the string and decodes it into FiniteDuration.

I would like it if I could generate JSON schema for my case class that contains FiniteDuration. To do that, I need to be able to write a custom json.Schema[FiniteDuration] which acts as json.Schema[String] with validation. Or, just a way to swap json.Schema[FiniteDuration] with json.Schema[String].

So some way to write custom json.Schema class would be nice, or else just a way to swap json.Schema[FiniteDuration] with json.Schema[String]. Currently, the second option doesn't work due to type checking.

allow arbitrary keys for Maps

MapKeyPattern[T] { def pattern: String } typeclass binds Key Type to pattern that will then be used in patternProperties

Validation not compiling for value class

I have a value class:

case class PartnerBucket(val value: String) extends AnyVal with StringField with Validates[PartnerBucket] {
  def isValid: Boolean = !value.isEmpty && PartnerBucket.valueRegExp.findFirstIn(value).isDefined
}
object PartnerBucket {
  val valueRegExp: Regex = "^(s3://|gs://)?[a-z-]+$".r
}

And try to define a json-schema withValidation for this:

  implicit val partnerBucketSchema: json.Schema[PartnerBucket] = Json.schema[PartnerBucket]("partnerBucket") withValidation (
    `pattern` := PartnerBucket.valueRegExp.toString()
    )

But get this error: Implicit not found: ValidationBound[com.liveintent.dwh.common.fields.PartnerBucket, String]. Some of validations doesn't match schema type

If I don't add the implicit schema PartnerBucket works correctly without validation as part of a bigger schema.

How do I declare a ValidationBound? Or how do I implement validations like this?

Creating Schema for custom types

Hello,

Any idea how do I define a schema when using cats NonEmptyList.

import cats.data.NonEmptyList
case class Error(errorCode: String, desc: String)
object Error {
  implicit val schema: json.Schema[Error] = json.Json.schema[Error]
}

case class Data(errors: Option[NonEmptyList[Error]])

object Data {
  implicit val schema: json.Schema[Data] = json.Json.schema[Data]
}

I currently get this error-

schema for A is not supported, cats.data.NonEmptyList[Error]

Looking at SchemaMacro.resolve, I see the types defined. There is Iterable and Array. But, I am not able to figure out a way to create a schema where I want to treat NonEmptyList as an Iterable because its not really implementing Iterable.

Thanks for any help.

README examples need more clarification

The documentation on the front page makes it difficult to pick up as a new user:

import json._

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

isn't going to work because Json isn't found. It isn't imported and searching for a Json type within the repo doesn't help. Perhaps a compilable examples module or be more explicit in your readme?

schema.apply is confusing

schema("foo") updates schema with a refName, which might confuse as not always schema which has refName lands into definition section. ref actually is what come to definition.
so api needs to be improved on this regard.

#80 mentions this confusion

Generic Schema Derivation Framework

There is a number of areas where schema derivation may also be required except json.

  • Apache Avro
  • Google Protobuf
  • Apache Spark StructType
  • Amazon Ion
  • Flatbuffers

Investigate, can the approach used in scala-jsonschema be used in order to derive other types of schemas.

scala.js support?

I can help with the cross-compiling but would like to check if it is something you guys are not against.

Feature Request: support Map[EnumEntry, V] of enumeratum's EnumEntry

Hi @andyglow,

currently only Map[String, T] is supported, would it be possible to support Map[EnumEntry, V]?

We use enumeratum library to build an enum type.
This type is used as key in a Map. When trying to build a json schema out of this it fails with:

scala.collection.immutable.Map takes 2 type parameters, expected: 1

Mapping EnumEntry to String for json should be easy I think, because every EnumEntry defines def entryName: String

Getting schema from companion object will return companion object's first apply parameter

If a case class has a companion object, it seems like we are unable to get the case class' parameters if there is an apply method with a parameter defined. The json schema will return a schema with the parameter's schema as the properties. See below.

  case class multiparam(p1: String, p2: Int, p3: Double)

  case class t1(foo: String, bar: String)
  object t1 {
    def apply(): t1 = t1("","")
    def apply(foobar: multiparam): t1 = t1("foo","bar")
    def apply(int: Int): t1 = t1("1","2")
  }

  case class t2(foo: String, bar: String)
  object t2 {
    def apply(): t2 = t2("","")
    def apply(int: Int): t2 = t2("1","2")
    def apply(foobar: multiparam): t2 = t2("foo","bar")
  }

  case class t3(foo: String, bar: String)
  object t3 {
    def apply(): t3 = t3("","")
  }


  def main(args: Array[String]): Unit = {
    def pprint(obj: json.Schema[_]) = { println(s"\n\n=====================\n${JsonFormatter.format(AsValue.schema(obj))}")}

    pprint(Json.schema[t1])
    pprint(Json.schema[t2])
    pprint(Json.schema[t3])
  }

yields

=====================
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "foobar": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "p1": {
          "type": "string"
        },
        "p2": {
          "type": "integer"
        },
        "p3": {
          "type": "number"
        }
      },
      "required": [
        "p1",
        "p2",
        "p3"
      ]
    }
  },
  "required": [
    "foobar"
  ]
}


=====================
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "int": {
      "type": "integer"
    }
  },
  "required": [
    "int"
  ]
}


=====================
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "foo": {
      "type": "string"
    },
    "bar": {
      "type": "string"
    }
  },
  "required": [
    "foo",
    "bar"
  ]
}

Is there any way to get the schema of the parameters of the case class (like t3) if there is an apply function with parameters already?

Support for recursive types?

The README mentions support for recursive types. But with my simple example below, I get a StackOverflowError at compile time. Am I missing anything?

case class Foo(a: Bar, b: String)
case class Bar(c: Foo, d: String)

val recursiveSchema = Json.schema[Foo].asCirce(Draft04())
println(recursiveSchema.toString())

Feature.Pack (Json.Profile ???)

h1. Problem
There are many scala libraries exists that solves parse/format problem for different scala types.
Not all of them follow the same standard, though. For example:

  • ujson libs treat scala.Option as json-array (by default)
  • some other use json.null for scala.None, whereas others just omit the field (when it comes to case class field >> json-object field
  • some libs may have support for scala.Either to json codecs, when the others haven't
  • some libs support recursive types, some others don't
  • discriminator field for sealed traits is treated differently

As of now scala-jsonschema has a single perspective on

  • a feature-set
  • and on how scala type is converted into json.
    Which leads to desynchronization. What derived schema shows basically not always coincides with what codec produces.

h1. Idea
scala-jsonschema should be flexible on this and provide extensible api so lib developer and/or extension developer may instruct it about what features supported, implementation specific of certain features and so on.

case class FeaturePack( // JsonProfile?
  `feature-xxx-enabled`: Boolean,
  `feature-yyy-details`: FeatureYYYDetails,
  ...
)

The existence of instance of this class in implicit scope of macro call should affect the way derivation logic process certain types.

Default values in Scala case class should be added as default in json-schema

When I create a json-schema from a case class with default values in Scala like:

case class Brilliant(param1: Boolean = true, param2: String = "MyHotString") extends Model

The properties in json-schema should have these added as "default" like:

    "properties" : {
      "param1" : {
        "type" : "boolean",
        "default": true
      },
      "param2" : {
        "type" : "string",
        "default": "MyHotString"
      },

Publish 0.0.9

Could you please publish version 0.0.9 of this library? The additional work on sealed traits was very helpful!

support of self-referenced case classes

I have an use-case that may be beneficial from this feature. I am wondering what's the plan here? I am willing to help but not familiar with the code base.

Supporting multi level sealed traits

I've found that the macro works for sealed traits, but if they are layered it fails:

sealed trait Animal {}
sealed trait Mammal extends Animal {}
val s = Json.schema[List[Animal]]

I looked into the code, but couldn't grasp how sealed traits are handled, so I cannot offer a PR for fixing this.

add support for sealed value class families

sealed trait Val extends Any
case class IntVal(value: Int) extends AnyVal with Val
case class BoolVal(value: Boolean) extends AnyVal with Val
case class StrVal(value: String) extends AnyVal with Val

for now such a family will generate a schema of oneof for a set of object having one value field each.. so the generation is done this way regardless the fact that members are actually value classes

this needs to be fixed

SchemaMacro doesn't work on Windows with CRLF

We recently updated to scala-jsonschema v. 0.2.7-M2 using the following libraries: (scala-jsonschema, scala-jsonschema-core, scala-jsonschema-macros, scala-jsonschema-circe-json). With this update, Windows users with CRLF set as line separator in IntelliJ (and otherwise in code) experienced the following error:

[error] C:\Users\chris\IdeaProjects\dwh\dwh-input\src\main\scala\com\liveintent\dwh\common\fields\PartnerBucket.scala:40:77: exception during macro expansion: [error] java.lang.StringIndexOutOfBoundsException: String index out of range: -49 [error] at java.lang.String.<init>(String.java:196) [error] at com.github.andyglow.jsonschema.SchemaMacro$Implicit$2$.isSelfRef(SchemaMacro.scala:392) [error] at com.github.andyglow.jsonschema.SchemaMacro$Implicit$2$.lookupSchema$1(SchemaMacro.scala:424) [error] at com.github.andyglow.jsonschema.SchemaMacro$Implicit$2$.getOrElse(SchemaMacro.scala:429) [error] at com.github.andyglow.jsonschema.SchemaMacro$.com$github$andyglow$jsonschema$SchemaMacro$$resolve$1(SchemaMacro.scala:455) [error] at com.github.andyglow.jsonschema.SchemaMacro$.impl(SchemaMacro.scala:458) [error] implicit val partnerBucketSchema: json.Schema[PartnerBucket] = Json.schema[PartnerBucket]("partnerBucket") withValidation (

This is solved by switching to LF in the code/IntelliJ, but is an unfortunate side effect of the M2 hotfix.

Need help customizing

Hi, I'm trying to support other types of containers in the most generic way.

To keep the example simple, lets assume we want to get schema for java.util.List[T] in a generic fashion.

I can manage to do this for simple predefs:

case class Test(listString: java.util.List[String])
implicit def javaList[T](implicit p: Predef[T]): Predef[java.util.List[T]] = Predef(`array`[T, java.util.List](p.schema))

will generate something like

    "listString": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },

However, I don't know how to make it accept any type T. I would like to declare an implicit schema for type T.
For example, this won't compile:

  case class Value(strVal: String)
  case class Test(listValue: java.util.List[Value])

 implicit def javaList[T](implicit p: Predef[T]): Predef[java.util.List[T]] = Predef(`array`[T, java.util.List](p.schema))
 implicit val valueSchema = Json.schema[Value]("Value")

I can manage to get array of Value but it's inlined:

    // needed for listString
    implicit def javaList[T](implicit p: Predef[T]): Predef[java.util.List[T]] = Predef(`array`[T, java.util.List](p.schema))

    // needed for listValue
    implicit def javaListSchema[T](implicit ss: Schema[T]): Predef[java.util.List[T]] = Predef(`array`[T, java.util.List](ss))

    // I would like to have Value in the definitions
    implicit val valueSchema = Json.schema[Value]("Value")

Produce:

    "listValue": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "strVal": {
            "type": "string"
          }
        },
        "required": [
          "strVal"
        ]
      }

Json (capital J) is not included with import json._

I can't figure out how to actually make a schema because the example on the readme seems to be incorrect. import json._ works to import json, which gives me access to the json.Schema type as expected. However, I cannot create a schema with Json.schema because Json is not included in that import at all.

I'm using this version:

<dependency>
  <groupId>com.github.andyglow</groupId>
  <artifactId>scala-jsonschema_2.11</artifactId>
  <version>0.5.0</version>
</dependency>

Is it possible there's some difference in the exports that is not reflected in the documentation?

base classes

Hi,

I'm wondering should it pick the value x inside TBase ?

sealed trait TBase { var x: Long = 0 }
case class Derived(var y: String) extends TBase

val jsonSchema = Json.schema[Derived]
println(JsonFormatter.format(AsValue.schema(jsonSchema, json.schema.Version.Draft04())))

Gives the result:

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

Custom name/definition only evaluates the first one

Firstly - thanks for writing this excellent library.

I'm seeing some slightly odd behavior when providing a custom name in order to "force" a definition within the parent schema, it seems that only the first schema definition is evaluated into a definition. Is this behavior expected?

This code:

import com.github.andyglow.json.{JsonFormatter, Value}
import com.github.andyglow.jsonschema.AsValue
import json.schema.Version.{Draft04, Draft07}
import json._

case class UserId(value: Int) extends AnyVal
case class UserName(value: String) extends AnyVal
case class User(id: UserId, name: UserName)

object Foo {
  implicit val uidSchema: json.Schema[UserId] = Json.schema[UserId]("user_id")
  implicit val uNameSchema: json.Schema[UserName] = Json.schema[UserName]("user_name")
  implicit val userSchema: json.Schema[User] = Json.schema[User]
  val draft04Schema: Value.obj = AsValue.schema(userSchema, Draft04())
  val draft07Schema: Value.obj = AsValue.schema(userSchema, Draft07(id = "the_user_schema"))
  def formatter(s: Value.obj): String = JsonFormatter.format(s)
}

Produces:

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

Whereas if the order of the sub-schemas is reversed >

  implicit val uNameSchema: json.Schema[UserName] = Json.schema[UserName]("user_name")
  implicit val uidSchema: json.Schema[UserId] = Json.schema[UserId]("user_id")

I get:

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

I would expect both of these sub-schemas to be referenced as definitions - or am I doing something wrong?

Running scala-jsonschema 0.5.0 and scala 2.13.2

value asInstanceOf is not a member of <notype> when using refined NonEmptyString

I'm seeing this error when trying to generate json schema for case class using refined NonEmptyString type:

value asInstanceOf is not a member of <notype>
Note that <none> extends Any, not AnyRef.
Such types can participate in value classes, but instances
cannot appear in singleton types or in reference comparisons.

When changing NonEmptyString to String everything works fine

example code:

import com.github.andyglow.json.JsonFormatter
import com.github.andyglow.jsonschema.AsValue
import com.github.andyglow.jsonschema.RefinedSupport._
import eu.timepit.refined.types.string.NonEmptyString
import json.{Json, Schema}

case class MyTest(string: NonEmptyString)

implicit val myTestSchema: Schema[MyTest] = Json.schema[MyTest]

println(JsonFormatter.format(AsValue.schema(myTestSchema, json.schema.Version.Draft04())))

I'm using scala 2.13.3 with scala-jsonschema 0.2.8

I thought NonEmptyString should work even not being mentioned in the ReadMe explicitly but
eu.timepit.refined.boolean.Not and eu.timepit.refined.collection.Empty are supported.

Scaladoc support is fragile

For example, place a quote in your scaladoc:

/** This is an example to show some "broken" things.
 * 
 * @param foo "Quotes" break JSON schema output
 * @param bar Regex backslash break JSON schema output
 */
case class Foobar( foo: String,   bar: String Refined MatchesRegex[W.`"""\\d{3}[- ]\\d{3}[- ]\\d{4}"""`.T])

You'll end up with invalid JSON in your schema output that cannot be parsed because " should have been escaped in the schema JSON output.

The same is true for using regexes with escapes.

For example, the \d must be escaped to feed the string to the regex parser, but using \\d for a digit match will lead to \d being outputted into your JSON schema, which is not valid; the json schema needs the \d to be escaped as well, like \\d.

It seems like the simple solution would be to pass the generated JSON descriptions through an escape function before flushing them to the schema output.

Optional field should have additional `null` as type

First of all, thank you for such a great library and hard work!

I am having issue validation json with optional fields with null values. As mentioned in everit-org/json-schema#16 optional values should have "type": "null" as valid value. However, this library does not add null as one of the possible values.

Note: I use circe for json + circe-schema(wrapper around everit) to validate schema

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

  case class Example(a: String, b: Option[String])
  implicit val exampleSchema: Schema[Example] = json.Json.schema[Example]
  val validator                               = io.circe.schema.Schema.load(exampleSchema.asCirce(Draft07(id = "example")))

  println(Example("Hello", Some("World")).asJson.noSpaces)
  println(validator.validate(Example("Hello", Some("World")).asJson))
  println(Example("Hello", None).asJson.noSpaces)
  println(validator.validate(Example("Hello", None).asJson))

--------------------------------------------------------------------------------------------

  {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "example",
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "a": {
        "type": "string"
      },
      "b": {
        "type": "string"
      }
    },
    "required": [
      "a"
    ]
  }

  {"a":"Hello","b":"World"}
  Valid(())
  {"a":"Hello","b":null}
  Invalid(NonEmptyList(io.circe.schema.ValidationError$$anon$1: #/b: expected type: String, found: Null))

I did a quick look to source code but could not understand much. I feel like it might be an easy fix so please let me know if I can help somehow.

Thanks in advance!

ToValue changes in 0.2.2 introduces a regression

Compiling against

		// json-schema generation support
		libraryDependencies += "com.github.andyglow" %% "scala-jsonschema-core" % "0.2.1",
		libraryDependencies += "com.github.andyglow" %% "scala-jsonschema-api" % "0.2.1",
		libraryDependencies += ("com.github.andyglow" %% "scala-jsonschema-circe-json" % "0.2.1")
			.excludeAll(ExclusionRule("io.circe")),

I can compile a schema

val schemaJson: Json = Json.schema[List[PartnerConfiguration]].asCirce(Version.Draft07(id = s"http://liveintent.com/config-$version.json$timestamp"))

on a case class like:

case class PartnerConfiguration (
                                              name: String,
                                              status: DeliveryStatus,
                                              primaryKey : EncryptionKey,
                                              extraKeys : List[EncryptionKey] = List(),
                                              pairDeliveries : List[PairDelivery] = List()
)

But updating the dependencies to 0.2.2 to I get

Can't infer a json value for extraKeys
06:36:00  [error]     val schemaJson: Json = Json.schema[List[PartnerConfiguration]].asCirce(Version.Draft07(id = s"http://liveintent.com/config-$version.json$timestamp"))

I can declare a custom ToValue like:

  implicit val encryptionKey: ToValue[EncryptionKey] = ToValue mk {
    key => Value.num(key.value.toInt)
  }

And then the compiler moves on to complain about pairDeliveries.

But PairDelivery is much more complex object and I feel it is a regression from 0.2.1 that I need to declare these...

Making the Lists Arrays to match v0.2.1...master#diff-b0136c6356f80a307a3ef4f66f85f837R37 didn't help.

Can you fix this regression in the framework? Or can I implement a ToValue for PairDelivery that simply uses the default ToValue for the sub-objects?

Add scaladoc as description in the schema

I understand you want to keep the API as strictly by-convention so the same case classes can be used for multiple serializations.

IntelliJ adds popups with the json schema descriptions when work with json validated with a schema.

It would be a helpful and very useful feature if you took the Scaladoc for the class on the objects and @param for the individual fields.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.