json deserializer with support for parameterized Case Classes - json

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

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.

Cannot find implicit reads for Coproduct

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.

couldn't decode Object with field Type Map[String, String] using circe

I have an case class which contains field of type Map[String, String]
The complete class definition is -
case class MyConfig(version: Int, pairs: Map[String, String]).
The json I'm trying to decode is -
{
"version":1,
"pairs":[
{
"key1":"value1",
"key2":"value2"
}
]
}
When I try to decode the string into MyConfig object println(decode[MyConfig](jsonStr)) I get the below error -
Left(DecodingFailure([K, V]Map[K, V], List(DownField(pairs)))).
The complete code is -
case class MyConfig(version: Int, pairs: Map[String, String])
import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
val jsonStr = """ {
| "version":1,
| "pairs":[
| {
| "key1":"value1",
| "key2":"value2"
| }
| ]
| } """.stripMargin
println(jsonStr)
println(decode[MyConfig](jsonStr))
I'm able to decode Map json object as demonstrated here but not object with map field.
Any idea how to resolve this error?
The problem is that the generically derived decoder will try to parse the "pairs" value as a Map[String, String], which means it will look for a JSON object, while what you have is a JSON array of objects.
If you're stuck with that MyConfig definition and input that's shaped like that, you're probably best off writing your own decoder instead of deriving one with io.circe.generic.auto. This is pretty straightforward with forProductN:
case class MyConfig(version: Int, pairs: Map[String, String])
import io.circe.Decoder
implicit val decodeMyConfig: Decoder[MyConfig] =
Decoder.forProduct2[MyConfig, Int, List[Map[String, String]]](
"version",
"pairs"
) {
case (v, ps) => MyConfig(v, ps.flatten.toMap)
}
And then, assuming you've defined jsonStr as above:
scala> import io.circe.parser.decode
import io.circe.parser.decode
scala> decode[MyConfig](jsonStr)
res0: Either[io.circe.Error,MyConfig] = Right(MyConfig(1,Map(key1 -> value1, key2 -> value2)))
Alternatively you could either change MyConfig so that the pairs member is a List[Map[String, String]], or you could change the JSON schema (or whatever code is generating it) to omit the JSON array layer.

Rename JSON fields with circe

I want to have different names of fields in my case classes and in my JSON, therefore I need a comfortable way of renaming in both, encoding and decoding.
Does someone have a good solution ?
You can use Custom key mappings via annotations. The most generic way is the JsonKey annotation from io.circe.generic.extras._. Example from the docs:
import io.circe.generic.extras._, io.circe.syntax._
implicit val config: Configuration = Configuration.default
#ConfiguredJsonCodec case class Bar(#JsonKey("my-int") i: Int, s: String)
Bar(13, "Qux").asJson
// res5: io.circe.Json = JObject(object[my-int -> 13,s -> "Qux"])
This requires the package circe-generic-extras.
Here's a code sample for Decoder (bit verbose since it won't remove the old field):
val pimpedDecoder = deriveDecoder[PimpClass].prepare {
_.withFocus {
_.mapObject { x =>
val value = x("old-field")
value.map(x.add("new-field", _)).getOrElse(x)
}
}
}
implicit val decodeFieldType: Decoder[FieldType] =
Decoder.forProduct5("nth", "isVLEncoded", "isSerialized", "isSigningField", "type")
(FieldType.apply)
This is a simple way if you have lots of different field names.
https://circe.github.io/circe/codecs/custom-codecs.html
You can use the mapJson function on Encoder to derive an encoder from the generic one and remap your field name.
And you can use the prepare function on Decoder to transform the JSON passed to a generic Decoder.
You could also write both from scratch, but it may be a ton of boilerplate, those solutions should both be a handful of lines max each.
The following function can be used to rename a circe's JSON field:
import io.circe._
object CirceUtil {
def renameField(json: Json, fieldToRename: String, newName: String): Json =
(for {
value <- json.hcursor.downField(fieldToRename).focus
newJson <- json.mapObject(_.add(newName, value)).hcursor.downField(fieldToRename).delete.top
} yield newJson).getOrElse(json)
}
You can use it in an Encoder like so:
implicit val circeEncoder: Encoder[YourCaseClass] = deriveEncoder[YourCaseClass].mapJson(
CirceUtil.renameField(_, "old_field_name", "new_field_name")
)
Extra
Unit tests
import io.circe.parser._
import org.specs2.mutable.Specification
class CirceUtilSpec extends Specification {
"CirceUtil" should {
"renameField" should {
"correctly rename field" in {
val json = parse("""{ "oldFieldName": 1 }""").toOption.get
val resultJson = CirceUtil.renameField(json, "oldFieldName", "newFieldName")
resultJson.hcursor.downField("oldFieldName").focus must beNone
resultJson.hcursor.downField("newFieldName").focus must beSome
}
"return unchanged json if field is not found" in {
val json = parse("""{ "oldFieldName": 1 }""").toOption.get
val resultJson = CirceUtil.renameField(json, "nonExistentField", "newFieldName")
resultJson must be equalTo json
}
}
}
}