Parsing a recursive trait playsafe json scala - json

I am using play-json_2.11, and I am trying to parse recursively some case classes
sealed trait Tree
case class Node(value: Float, child: Seq[Tree]) extends Tree
case class Leaf(leaf: Float) extends Tree
So basically, each Node contains a value and a list of Trees (which can be a Node or a Leaf).
So I am defining implicit readers in the companion objects of the case classes. and one in the object called Tree
object Node {
implicit val reader = Json.reads[Node]
}
object Leaf {
implicit val reader = Json.reads[Leaf]
}
object Tree {
implicit val treeReads =
__.read[Node].map(x => x:Tree) orElse __.read[Leaf].map(x => x:Tree)
}
As the parsers are referencing to each other, I cannot define them and get the following error:
ScalaFiddle.scala:9: error: No instance of play.api.libs.json.Reads is available for scala.Seq[ScalaFiddle.Tree] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
implicit val reader = Json.reads[Node]
How can I parse a Tree in this case? (I do not need it to be specifically a Trait)
Here is the fiddle I tried https://scalafiddle.io/sf/sX8OkWI/3
My input is a json like this one
{
"value": 1.0,
"child": {
"leaf": 2.0
}
}
And I would like to parse it to have
Node(1.0, Leaf(2.0))

This is what you need
import play.api.libs.json._
import play.api.libs.functional.syntax._
sealed trait Tree
case class Node(value: Float, child: Tree) extends Tree
object Node {
implicit lazy val reader = Json.reads[Node]
}
case class Leaf(leaf: Float) extends Tree
object Leaf {
implicit lazy val reader = Json.reads[Leaf]
}
object Tree {
implicit lazy val treeReads: Reads[Tree] =
__.lazyRead(Node.reader).map(x => x:Tree) orElse __.lazyRead(Leaf.reader).map(x => x:Tree)
}
val json: JsValue = Json.parse("""
{
"value": 5.0,
"child": {
"leaf": 7
}
}
""")
println(json)
json.validate[Tree] match {
case s: JsSuccess[Tree] => {
val place: Tree = s.get
println(place)
}
case e: JsError => {
println(e)
}
}

You don't need the implicits in the companion objects.. or objects for that matter:
import play.api.libs.json._
import play.api.libs.functional.syntax._
sealed trait Tree
case class Node(value: Double, child: Tree) extends Tree
case class Leaf(leaf: Double) extends Tree
val json: JsValue = Json.parse("""
{
"value": 1.0,
"child": {
"leaf": 2.0
}
}
""")
implicit val nReader = Json.reads[Node]
implicit val lReader = Json.reads[Leaf]
implicit lazy val treeReads: Reads[Tree] =
__.lazyRead(nReader).map(x => x:Tree) orElse __.lazyRead(lReader).map(x => x:Tree)
json.validate[Tree] match {
case s: JsSuccess[Tree] => {
val place: Tree = s.get
println(place)
}
case e: JsError => {
println(e)
}
}
https://scalafiddle.io/sf/sX8OkWI/13

Related

Play Json Reads nested generic serialized Json

Consider the following JSON
{
"a": "{\"b\": 12, \"c\": \"test\"}"
}
I would like to define a generic reads Reads[Outer[T]] for this kind of serialized Json
import play.api.libs.json.{Json, Reads, __}
final case class Outer[T](inner: T)
final case class SpecializedInner(b: Int, c: String)
object SpecializedInner {
implicit val reads: Reads[SpecializedInner] = Json.reads[SpecializedInner]
}
object Outer {
implicit def reads[T](implicit readsT: Reads[T]): Reads[Outer[T]] = ???
}
How can I achieve my goal? I tried to flatMap a string-reads for the field "outer.a" but got stuck since I am not able to produce a Reads[T] from the validated JSON
object Outer {
implicit def reads[T](implicit readsT: Reads[T]): Reads[Outer[T]] =
(__ \ "a").read[String].flatMap(x => Json.parse(x).validate[T])
}
You need just add map inside Outer.reads construction after validate[T] invocation.
Please, see next code for example:
object App {
final case class Outer[T](inner: T)
object Outer {
implicit def reads[T](implicit innerReads: Reads[T]): Reads[Outer[T]] = { json: JsValue =>
json.validate[String].flatMap(string => Json.parse(string).validate[T].map(Outer.apply[T]))
}
}
final case class Root[T](a: Outer[T])
object Root {
implicit def reads[T](implicit innerReads: Reads[T]): Reads[Root[T]] = Json.reads
}
final case class SpecializedInner(b: Int, c: String)
object SpecializedInner {
implicit val reads: Reads[SpecializedInner] = Json.reads
}
def main(args: Array[String]): Unit = {
val rawJson = "{\"a\": \"{\\\"b\\\": 12, \\\"c\\\": \\\"test\\\"}\"}"
println(Json.parse(rawJson).validate[Root[SpecializedInner]])
}
}
Which produced next result in my case:
JsSuccess(Root(Outer(SpecializedInner(12,test))),)
Hope this helps!

Building a Json Format for a Case Class with Abstract Members

I am using the Play Framework and trying to build JSON validator for a class with abstract members. Shown below, the DataSource class is the base class which I am trying to validate the format against.
// SourceTypeConfig Trait.
trait SourceTypeConfig
final case class RDBMSConfig(...) extends SourceTypeConfig
object RDBMSConfig { implicit val fmt = Json.format[RDBMSConfig] }
final case class DirectoryConfig(
path: String,
pathType: String // Local, gcloud, azure, aws, etc.
) extends SourceTypeConfig
object DirectoryConfig { implicit val fmt = Json.format[DirectoryConfig] }
// FormatConfig trait.
trait FormatConfig
final case class SQLConfig(...) extends FormatConfig
object SQLConfig { implicit val fmt = Json.format[SQLConfig]}
final case class CSVConfig(
header: String,
inferSchema: String,
delimiter: String
) extends FormatConfig
object CSVConfig { implicit val fmt = Json.format[CSVConfig]}
// DataSource base class.
case class DataSource(
name: String,
sourceType: String,
sourceTypeConfig: SourceTypeConfig,
format: String,
formatConfig: FormatConfig
)
What I am hoping to accomplish:
val input: JsValue = Json.parse(
"""
{
"name" : "test1",
"sourceType" : "directory",
"sourceTypeConfig" : {"path" : "gs://test/path", "pathType" "google"},
"format" : "csv",
"formatConfig" : {"header" : "yes", "inferSchema" : "yes", "delimiter" : "|"}
}
"""
)
val inputResult = input.validate[DataSource]
What I am struggling with is building the DataSource object and defining its reads/writes/format. I would like it to contain a match based on the sourceType and format values that direct it to point towards the associated sourceTypeConfig and formatConfig's formats so it can parse out the JSON.
Instead of building a parser at the DataSource level, I defined parsers at the SourceConfig and FormatConfig levels, similar to what is shown below.
sealed trait SourceConfig{val sourceType: String}
object SourceConfig{
implicit val fmt = new Format[SourceConfig] {
def reads(json: JsValue): JsResult[SourceConfig] = {
def from(sourceType: String, data: JsObject): JsResult[SourceConfig] = sourceType match {
case "RDBMS" => Json.fromJson[RDBMSConfig](data)(RDBMSConfig.fmt)
case "directory" => Json.fromJson[DirectoryConfig](data)(DirectoryConfig.fmt)
case _ => JsError(s"Unknown source type: '$sourceType'")
}
for {
sourceType <- (json \ "sourceType").validate[String]
data <- json.validate[JsObject]
result <- from(sourceType, data)
} yield result
}
def writes(source: SourceConfig): JsValue =
source match {
case b: RDBMSConfig => Json.toJson(b)(RDBMSConfig.fmt)
case b: DirectoryConfig => Json.toJson(b)(DirectoryConfig.fmt)
}
}
}
Then, DataSource could be simply defined as:
object DataSource { implicit val fmt = Json.format[DataSource] }
Another option is to use play-json-derived-codecs library:
libraryDependencies += "org.julienrf" %% "play-json-derived-codecs" % "4.0.0"
import julienrf.json.derived.flat
implicit val format1: OFormat[RDBMSConfig] = Json.format[RDBMSConfig]
implicit val format2: OFormat[DirectoryConfig] = Json.format[DirectoryConfig]
implicit val format3: OFormat[SourceTypeConfig] = flat.oformat((__ \ "sourceType").format[String])

convert json to array of scala objects using spray json

I am not much familier with spray json, but I have to convert the below json into Array[myTest]
Below is the code, but it doesnt work. It throws the following errors: How do I fix them?
Error:(19, 54) Cannot find JsonReader or JsonFormat type class for Array[A$A61.this.myTest]
lazy val converted= trainingDataRef.toJson.convertTo[Array[myTest]]
^
Error:(19, 54) not enough arguments for method convertTo: (implicit evidence$1: spray.json.JsonReader[Array[A$A61.this.myTest]])Array[A$A61.this.myTest].
Unspecified value parameter evidence$1.
lazy val converted= trainingDataRef.toJson.convertTo[Array[myTest]]
^
Error:(10, 61) could not find implicit value for evidence parameter of type spray.json.DefaultJsonProtocol.JF[Map[String,Any]]
implicit val format: RootJsonFormat[myTest] = jsonFormat3(myTest.apply)
^
Code: ^
import spray.json.DefaultJsonProtocol._
import spray.json._
case class myTest (
id: String,
classDetails: Map[String, Any],
school: Map[String, Any])
object myTest {
implicit val format: RootJsonFormat[myTest] = jsonFormat3(myTest.apply)
}
val trainingDataRef = """[{"id":"my-id","classDetails":{"sec":"2","teacher":"John"},"school":{"name":"newschool"}}]"""
println(trainingDataRef.getClass)
val converted= trainingDataRef.toJson.convertTo[Array[myTest]]
println(converted)
spray-json has good documentation, try take a look there. Basically, you have to define your case classes and implement JsonFormat for them:
import spray.json.DefaultJsonProtocol._
import spray.json._
case class ClassDetails(sec: String, teacher: String)
object ClassDetails {
implicit val format: RootJsonFormat[ClassDetails] = jsonFormat2(ClassDetails.apply)
}
case class School(name: String)
object School {
implicit val format: RootJsonFormat[School] = jsonFormat1(School.apply)
}
case class ClassInfo
(
id: String,
classDetails: ClassDetails,
school: School
)
object ClassInfo {
implicit object ClassInfoFormat extends RootJsonFormat[ClassInfo] {
def write(c: ClassInfo): JsValue = JsObject(
"id" -> JsString(c.id),
"classDetails" -> c.classDetails.toJson,
"school" -> c.school.toJson
)
def read(value: JsValue): ClassInfo = {
value.asJsObject.getFields("id", "classDetails", "school") match {
case Seq(JsString(name), details, school) =>
new ClassInfo(name, details.convertTo[ClassDetails], school.convertTo[School])
case _ => throw new DeserializationException("ClassInfo expected")
}
}
}
}
val json = """[{"id":"my-id","classDetails":{"sec":"2","teacher":"John"},"school":{"name":"newschool"}}]"""
// JSON string to case classes
val classInfos = json.parseJson.convertTo[Seq[ClassInfo]]
classInfos.zipWithIndex.foreach { case (c, idx) =>
println(s"$idx => $c")
}
println
// Seq[ClassInfo] to JSON
println(s"$classInfos: ")
println(classInfos.toJson.prettyPrint)

No instance of play.api.libs.json.Format is available for scala.Predef.Map[java.lang.String, scala.Option[scala.Double]]

Trying to write a json format for an entity which contains a Map of Option. It throws following error
Error:(8, 68) No instance of play.api.libs.json.Format is available for scala.Predef.Map[java.lang.String, scala.Option[scala.Double]] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
Code snippet:
import play.api.libs.json.{Json, OFormat}
val a: Map[String, Option[Double]] = Map("a" -> None)
case class Person(x: Map[String, Option[Double]])
object Person {
implicit val personFormat: OFormat[Person] = Json.format[Person]
}
Json.toJson(Person(a))
You can define implicits:
import scala.collection.Map
object Person {
implicit object MapReads extends Reads[Map[String, Option[Double]]] {
def reads(jsValue: JsValue): JsResult[Map[String, Option[Double]]] = jsValue match {
case JsObject(map) => JsSuccess(
map.mapValues {
case JsNumber(d) => Some(d.toDouble)
case _ => None
}
)
case _ => JsError()
}
}
implicit object MapWrites extends Writes[Map[String, Option[Double]]] {
def writes(map: Map[String, Option[Double]]): JsValue =
JsObject(map.mapValues(optd => JsNumber(optd.getOrElse[Double](0.0))))
}
implicit val personFormat: OFormat[Person] = Json.format[Person]
}
println(Json.toJson(Person(a)))//{"x":{"a":0}}
Based on answer.
The problem seems to be the inability of play-json macros to work with nested type variables:
Map[String, Option[Double]]
You could use an intermediate type wrapping Option
import play.api.libs.json.{Json, OFormat}
case class OptionDouble(value: Option[Double])
case class Person(x: Map[String, OptionDouble])
implicit val optionDoubleFormat = Json.format[OptionDouble]
implicit val personFormat = Json.format[Person]
val a: Map[String, OptionDouble] = Map("a" -> OptionDouble(None))
Json.toJson(Person(a))
Another option could be to write the formatter by hand.

Json.writes[List[Foo]] as "map"

I have a case class Foo(bars: List[Bar]) who is rendered as json via Json inception as an object with an array :
{"bars": [
{
"key: "4587-der",
"value": "something"
}
]
}
But I want to render the bars: List[Bar] as a "map" where Bar.key is used as key :
{"bars":{
"4587-der": {
"value": "something"
}
}
}
How can I obtains that without modifying my case class Foo ?
Thanks a lot
You can define a Writes for Bar by extending Writes[Bar] and implementing a writes method for it:
case class Bar(key: String, value: String)
implicit object BarWrites extends Writes[Bar] {
def writes(bar: Bar): JsValue = Json.obj(
bar.key -> Json.obj("value" -> bar.value)
)
}
scala> Json.stringify(Json.toJson(Bar("4587-der", "something")))
res0: String = {"4587-der":{"value":"something"}}
For those that may be interested, here is a (somewhat) crude implementation of Reads[Bar]:
implicit object BarReads extends Reads[Bar] {
def reads(js: JsValue): JsResult[Bar] = js match {
case JsObject(Seq((key, JsObject(Seq(("value", JsString(value))))))) => JsSuccess(Bar(key, value))
case _ => JsError(Seq())
}
}
scala> Json.parse(""" [{"4587-der":{"value": "something"}}] """).validate[List[Bar]]
res11: play.api.libs.json.JsResult[List[Bar]] = JsSuccess(List(Bar(4587-der,something)),)
Edit, since the OP wants the Bars merged into an object rather than an array:
You'll also have to define a special Writes[List[Bar]] as well:
implicit object BarListWrites extends Writes[List[Bar]] {
def writes(bars: List[Bar]): JsValue =
bars.map(Json.toJson(_).as[JsObject]).foldLeft(JsObject(Nil))(_ ++ _)
}
scala> val list = List(Bar("4587-der", "something"), Bar("1234-abc", "another"))
list: List[Bar] = List(Bar(4587-der,something), Bar(1234-abc,another))
scala> Json.stringify(Json.toJson(list))
res1: String = {"4587-der":{"value":"something"},"1234-abc":{"value":"another"}}