Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schema for play-json JsValue #286

Open
0lejk4 opened this issue Sep 27, 2022 · 3 comments
Open

Schema for play-json JsValue #286

0lejk4 opened this issue Sep 27, 2022 · 3 comments

Comments

@0lejk4
Copy link

0lejk4 commented Sep 27, 2022

Hello @andyglow I saw https://github.com/andyglow/scala-jsonschema#free-objects with info on how to create the schema for JsObject and now I am struggling with creating Schema for JsValue. Can you suggest the code for its schema?
Also, have a question on how to have Schema for JsValue and its subtypes as because of Schema type-class covariance the behavior is strange, for example if I define schema for JsValue and JsNull, JsNull is used when Schema for JsValue is expected.

@andyglow
Copy link
Owner

Hello, @0lejk4
There's not much we can infer from JsValue type as it's abstract..
At the compile time we don't know how you want to represent it, right? It can be anything.
In json-schema would it would mean you need to use something like this:

"properties": {
  "my-abstract-prop": {}
}

Seems like this empty definition does the job.
I'm not sure if you can do it with the library as original idea was to represent strongly typed information.

You can do something though, Please check this

import json.Schema, json.Json, json.schema.Predef
import com.github.andyglow.jsonschema._
import play.api.libs.json._

object PlayJsonBindings {

  implicit val playJsonNumberPredef: Predef[JsNumber] = Predef.bigDecimalS.asInstanceOf[Predef[JsNumber]]
  implicit val playJsonStringPredef: Predef[JsString] = Predef.strS.asInstanceOf[Predef[JsString]]
  implicit val playJsonBooleanPredef: Predef[JsBoolean] = Predef.boolS.asInstanceOf[Predef[JsBoolean]]


  case class Payload(
    id: String,
    num: JsNumber,
    str: JsString,
    bool: JsBoolean
  )
  
  def main(args: Array[String]): Unit = {
		val schema: Schema[Payload] = Json.schema[Payload]
    println(schema.draft07("foo"))
  }
}

https://scastie.scala-lang.org/andyglow/ehjsHlLaTNCScxihwnjdiQ/7

This shows that you can expose json-type information from the types that you are aware of.

May I ask you to describe your case little bit more detailed?
Wonder why people would need to map json ast into schema.

@0lejk4
Copy link
Author

0lejk4 commented Oct 19, 2022

So the use case is having a field in DTO with some random JSON that we don't care about, but it then will be sent to someone who knows what to do with it.
Initially, I had an idea as you sent with empty schema and to add a description that this is plain JSON.
Another variant is to describe it like this:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "array",
  "items": {
    "$ref": "#/definitions/JsValue"
  },
  "definitions": {
    "JsObject": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/definitions/JsValue"
      },
      "title": "JsObject"
    },
    "JsValue": {
      "anyOf": [
        {
          "type": "array",
          "items": {
            "$ref": "#/definitions/JsValue"
          }
        },
        {
          "type": "boolean"
        },
        {
          "$ref": "#/definitions/JsObject"
        },
        {
          "type": "integer"
        },
        {
          "type": "null"
        },
        {
          "type": "string"
        }
      ],
      "title": "JsValue"
    }
  }
}

First is easier and enough for my use case but I still had issues with the covariance of the Schema typeclass. So I had schema for JsNull and JsValue, because of covariance they were colliding in implicit search, so I needed to use this trick but in the end in places where JsValue JsonSchema was expected I was getting JsNull`s schema.

@andyglow
Copy link
Owner

you can also try something like this

import json.Schema, json.Json
import com.github.andyglow.jsonschema._

object PlayJsonBindings {

	sealed trait Val extends Any
  object Val {
    // Null case is not supported by scala-jsonschema library
    // as it's hard to imagine the straight use case 
    case class Str(x: String) extends AnyVal with Val
    case class Num(x: Double) extends AnyVal with Val
    case class Bool(x: Boolean) extends AnyVal with Val
    case class Arr(x: Seq[Val]) extends AnyVal with Val
    case class Dict(x: Map[String, Val]) extends AnyVal with Val
  }
  
  def main(args: Array[String]): Unit = {
		val schema: Schema[Val] = Json.schema[Val]
    println(schema.draft07("foo"))
  }
}

which is getting resulted in

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "foo",
  "$ref": "#PlayJsonBindings.Val",
  "definitions": {
    "PlayJsonBindings.Val": {
      "$id": "#PlayJsonBindings.Val",
      "oneOf": [
        {
          "type": "number"
        },
        {
          "type": "string"
        },
        {
          "type": "boolean"
        },
        {
          "type": "object",
          "patternProperties": {
            "^.*$": {
              "$ref": "#PlayJsonBindings.Val"
            }
          }
        },
        {
          "type": "array",
          "items": {
            "$ref": "#PlayJsonBindings.Val"
          }
        }
      ]
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants