Cannot find implicit reads for Coproduct - json

I have the following situation.
import shapeless.{:+:, CNil}
import julienrf.json.derived.{DerivedOWrites, DerivedReads, NameAdapter}
case class A(i : Int)
case class B(s : String)
object Event {
type AllEvent = A :+: B :+: CNil
implicit def allFormats[T](implicit dr: DerivedReads[T], dw: DerivedOWrites[T]): OFormat[T] = derived.oformat[T](NameAdapter.snakeCase)
}
case class C(id : String, event : Event.AllEvent)
and I want to be able to parse the following json into a C
import play.api.libs.json.{Json, OFormat}
import Event.allFormats
val json = Json.parse(
"""{
"id" : "id",
"event":{
"i" : 1
}
}"""
).as[C]
However I'm getting the error
Error: No Json deserializer found for type C. Try to implement an implicit Reads or Format for this type.
).as[C]
It appears to be struggling to resolve a reads for A :+: B :+: CNil.
Whatever I try doesn't seem to work. Am I missing something obvious.

Related

How to know which field is missed while parsing json in Json4s

I have made a generic method which parses json to case class and it also works fine. But if tries to parse big json which have one or two mandatory field then I am not able to figure out which particular mandatory f ield is missing. I am only able to handle it with IllegalArgumentException. Is there a way to handle to know which is field is missing while parsing Json by using json4s.
Here is my code ->
object JsonHelper {
implicit val formats: DefaultFormats = DefaultFormats
def write[T <: AnyRef](value: T): String = jWrite(value)
def parse(value: String): JValue = jParser(value)
}
And this is the method I am using to parse Json and handle failed case ->
def parseJson[M](json: String)(implicit m: Manifest[M]): Either[ErrorResponse, M] = {
try
Right(JsonHelper.parse(json).extract[M])
catch {
case NonFatal(th) =>
th.getCause.getCause match {
case e: java.lang.IllegalArgumentException =>
error(s"Invalid JSON - $json", e)
Left(handle(exception = EmptyFieldException(e.getMessage.split(":").last)))
case _ =>
error(s"Invalid JSON - $json", th)
Left(handle(exception = new IllegalArgumentException("Invalid Json", th)))
}
}
}
Like for a Json ->
{
"name": "Json"
}
And case class ->
case class(name: String, profession: String)
if I try to parse above json into case class currently I am getting Invalid JSON - IllegalArgumentException. But is there a way that the exception tells which is field is missing like in above example "profession" is missing.
Is there a way to handle to know which is field is missing while parsing Json by using json4s.
Maybe you have more complicated setting, but for example for
import org.json4s._
import org.json4s.jackson.JsonMethods._
val str = """{
| "name": "Json"
|}""".stripMargin
val json = parse(str) // JObject(List((name,JString(Json))))
implicit val formats: Formats = DefaultFormats
case class MyClass(name: String, profession: String)
json.extract[MyClass]
it produces
org.json4s.MappingException: No usable value for profession
Did not find value which can be converted into java.lang.String
at org.json4s.reflect.package$.fail(package.scala:56)
at ...
with the name of missing field and if the class is just case class MyClass(name: String) then this produces MyClass(Json).
If the class is case class MyClass(name: String, profession: Option[String]) then this produces MyClass(Json,None).
So normally IllegalArgumentException should be followed by Caused by: org.json4s.MappingException with the field name. I guess now you're swallowing json4s MappingException somewhere. Maybe in th.getCause.getCause match .... It's hard to say without MCVE.

Scala - Couldn't remove double quotes for Key -> value "{}" braces while building Json

Scala - Couldn't remove double quotes for "{}" braces while building Json
import scala.util.Random
import math.Ordered.orderingToOrdered
import math.Ordering.Implicits.infixOrderingOps
import play.api.libs.json._
import play.api.libs.json.Writes
import play.api.libs.json.Json.JsValueWrapper
val data1 = (1 to 2)
.map {r => Json.toJson(Map(
"name" -> Json.toJson(s"Perftest${Random.alphanumeric.take(6).mkString}"),
"domainId"->Json.toJson("343RDFDGF4RGGFG"),
"value" ->Json.toJson("{}")))}
val data2 = Json.toJson(data1)
println(data2)
Result :
[{"name":"PerftestpXI1ID","domainId":"343RDFDGF4RGGFG","value":"{}"},{"name":"PerftestHoZSQR","domainId":"343RDFDGF4RGGFG","value":"{}"}]
Expected :
"value":{}
[{"name":"PerftestpXI1ID","domainId":"343RDFDGF4RGGFG","value":{}},{"name":"PerftestHoZSQR","domainId":"343RDFDGF4RGGFG","value":{}}]
Please suggest a solution
You are giving it a String so it is creating a string in JSON. What you actually want is an empty dictionary, which is a Map in Scala:
val data1 = (1 to 2)
.map {r => Json.toJson(Map(
"name" -> Json.toJson(s"Perftest${Random.alphanumeric.take(6).mkString}"),
"domainId"->Json.toJson("343RDFDGF4RGGFG"),
"value" ->Json.toJson(Map.empty[String, String])))}
More generally you should create a case class for the data and create a custom Writes implementation for that class so that you don't have to call Json.toJson on every value.
Here is how to do the conversion using only a single Json.toJson call:
import play.api.libs.json.Json
case class MyData(name: String, domainId: String, value: Map[String,String])
implicit val fmt = Json.format[MyData]
val data1 = (1 to 2)
.map { r => new MyData(
s"Perftest${Random.alphanumeric.take(6).mkString}",
"343RDFDGF4RGGFG",
Map.empty
)
}
val data2 = Json.toJson(data1)
println(data2)
The value field can be a standard type such as Boolean or Double. It could also be another case class to create nested JSON as long as there is a similar Json.format line for the new type.
More complex JSON can be generated by using a custom Writes (and Reads) implementation as described in the documentation.

json deserializer with support for parameterized Case Classes

Just encountered that liftweb.json does not work with parameterized Case Classes.
The following fails at runtime:
case class ResponseOrError[R](status: String, responseData: Option[R], exception: Option[Error]) {
}
val answer = json.extract[ResponseOrError[Response]]
with:
do not know how to get type parameter from R
Is there any JSON deserializer, which actually works with parameterized Case Classes?
json4s works the way you expect. Here is an example:
import org.json4s.{DefaultFormats, Formats}
import org.json4s.jackson.JsonMethods.parse
case class Z(str: String)
case class X[R](z: Option[R])
val json =
"""
|{
| "z": {
| "str" : "test"
| }
|}
""".stripMargin
implicit val formats: Formats = DefaultFormats.withStrictArrayExtraction
val result = parse(json).extract[X[Z]]
println(result)
output
X(Some(Z(test)))

Custom circe decoder for variant json-field

How can I write circe decoder for class
case class KeyValueRow(count: Int, key: String)
where json contains field "count" (Int) and some extra string-field (name of this field may be various, like "url", "city", whatever)?
{"count":974989,"url":"http://google.com"}
{"count":1234,"city":"Rome"}
You can do what you need like this:
import io.circe.syntax._
import io.circe.parser._
import io.circe.generic.semiauto._
import io.circe.{ Decoder, Encoder, HCursor, Json, DecodingFailure}
object stuff{
case class KeyValueRow(count: Int, key: String)
implicit def jsonEncoder : Encoder[KeyValueRow] = deriveEncoder
implicit def jsonDecoder : Decoder[KeyValueRow] = Decoder.instance{ h =>
(for{
keys <- h.keys
key <- keys.dropWhile(_ == "count").headOption
} yield {
for{
count <- h.get[Int]("count")
keyValue <- h.get[String](key)
} yield KeyValueRow(count.toInt, keyValue)
}).getOrElse(Left(DecodingFailure("Not a valid KeyValueRow", List())))
}
}
import stuff._
val a = KeyValueRow(974989, "www.google.com")
println(a.asJson.spaces2)
val test1 = """{"count":974989,"url":"http://google.com"}"""
val test2 = """{"count":1234,"city":"Rome", "will be dropped": "who cares"}"""
val parsedTest1 = parse(test1).flatMap(_.as[KeyValueRow])
val parsedTest2 = parse(test2).flatMap(_.as[KeyValueRow])
println(parsedTest1)
println(parsedTest2)
println(parsedTest1.map(_.asJson.spaces2))
println(parsedTest2.map(_.asJson.spaces2))
Scalafiddle: link here
As I mentioned in the comment above, keep in mind that the that if you decode some json, and then re-encode it, the result will be different from the initial input. To fix that, you would need to keep track of the original name of the key field.

Removing top level field in json4s when nested field with similar name exists

I would like to remove a top level field called "id" in a json structure, without removing all fields named "id", which happens when I run the following code:
scala> import org.json4s._
import org.json4s._
scala> import org.json4s.native.JsonMethods._
import org.json4s.native.JsonMethods._
scala> import org.json4s.JsonDSL._
import org.json4s.JsonDSL._
scala> val json = parse("""{ "id": "bep", "foo": { "id" : "bap" } }""")
json: org.json4s.JValue = JObject(List((id,JString(bep)), (foo,JObject(List((id,JString(bap)))))))
scala> json removeField {
| case ("id", v) => true
| case _ => false
| }
res0: org.json4s.JValue = JObject(List((foo,JObject(List()))))
Any idea how I can avoid removing the inner "id" field?
Edit: unfortunately I do not have the ability to list all the possible top level objects the json contains or can contain.
It seems like removeField applies to the entire JSON tree.
This works only for the top-level:
val updated = json match {
case JObject(l) => JObject(l.filter {
case (name, _) => name != "id"
})
}
Based on the answer here you could do something like this:
val transformedJson2 = json transform {
case JField("id", _) => JNothing
case JField("foo", fields) => fields
}
It is definitely not ideal because you would have to specify all the foo elements with a sub element id
To do this without the need to know the schema of other fields:
JObject(
json.asInstanceOf[JObject].obj.filterNot(_._1 == "id")
)
JObject().obj is the flat list of fields that compose the object.
This doesn't seem possible staying within the json4s DSL though.