andyglow / scala-jsonschema

Scala JSON Schema
Other
123 stars 38 forks source link

json schema patternProperties support #197

Closed mpindado closed 3 years ago

mpindado commented 3 years ago

Hi, we currently have some scala case classes we are using with circe for serializing. What we are trying to achieve is to automatically generate a schema using scala-jsonschema, however, we are having difficulties trying to understand the library when we have derived types. Please consider the following example:

sealed trait DataFormat
case class CsvFormat(delimiter:String) extends DataFormat
case class XmlFormat(charset:String) extends DataFormat
case class File(name:String, format:DataFormat)

Depending on how we configure circe, we can have different representations of these json:

{"name":"csvfile","format":{"CsvFormat":{"delimiter":"|"}}
{"name":"csvfile","format":{"type": "CsvFormat", "delimiter": "|"}}
{"name":"csvfile","format":{"type": "XmlFormat", "charset": "utf8"}}

When we generate a schema for these classes, we get something like this:

  "properties": {
    "name": {
      "type": "string"
    },
    "format": {
      "$ref": "#entities.DataFormat"
    }
  },
...
  "$defs": {
    "entities.DataFormat": {
      "$anchor": "entities.DataFormat",
      "type": "object",
      "oneOf": [
        {
          => inline definition for xmlformat or csvformat

However, we cannot find any literal referencing "CsvFormat" or "XmlFormat" in the schema definition. We would like to include this so that it is not only a validation framework but for documentation.

According the json schema spec, maybe we could use "patternProperties" for the first representation, to get something like this:

  "patternProperties": {
    "^(CsvFormat$)": {REF TO CSV FORMAT},
    "^(XmlFormat$)": {REF TO XML FORMAT}
  }

Is this viable with the library?

Regarding the second definition, we think we could use the @discriminator annotation but here we find 2 issues. The first is that setting discriminator to the root prints the whole package of the class:

           "type": {
              "enum": [
                "entities.XmlFormat" => Can we get rid of "entities"?
              ]
            }

The second issue is that sometimes we do not have access to the source to add the annotation. In #99 , there is an example to use the discriminator without annotations, but, we don't know how to apply to our particular case, this naive try does not work:

implicit val dataFormatSchema = Json.schema[DataFormat].withDiscriminationKey("type")
val fileSchema = Json.schema[File]

So is this possible to add the discriminator in such a way?

Best regards

mpindado commented 3 years ago

Leave here our final solution, as it seems patternProperties only match property names and not schemas.

@discriminator sealed trait DataFormat
@discriminatorKey("csv") case class CsvFormat(delimiter:String) extends DataFormat
@discriminatorKey("xml") case class XmlFormat(charset:String) extends DataFormat
case class File(name:String, format:DataFormat)

Please note annotations are in the same line as the class due to #198 .

This generates a "type" parameter in the schema with csv or xml as enum types for each of the alternatives.

Edit: Also we have used the @definition annotation for non-inline type definitions:

@discriminatorKey("csv") @definition("CsvFormat") case class CsvFormat(delimiter:String) extends DataFormat

I think these annotations should be somewhere in the README.