Hello I would like to transform json object into map using implicit Reads.
With the code below I run into a StackOverflow error, any one could see what the problem is :
"pass": {
"key-1": {
"field1": "aaaa",
"field2": "aaaa"
},
"key-2": {
"field1": "aaaa",
"field2": "aaaa"
},
"key-3": {
"field1": "aaaa",
"field2": "aaaa"
}
}
case class Pass(field1: String, field2: String)
implicit val mapReads: Reads[Map[String, Pass]] = new Reads[Map[String, Pass]] {
def reads(jv: JsValue): JsResult[Map[String, Pass]] =
JsSuccess(jv.as[Map[String, Pass]].map{
case (k, v) => k -> v.asInstanceOf[Pass]
})
}
val passMap = (json \ "pass").validate[Map[String, Pass]]
Here is the stack error :
java.lang.StackOverflowError
at play.api.libs.json.JsReadable$class.as(JsReadable.scala:23)
at play.api.libs.json.JsObject.as(JsValue.scala:124)
at com.MyHelper$$anon$1.reads(MyHelper.scala:51)
at play.api.libs.json.Format$$anon$3.reads(Format.scala:65)
at play.api.libs.json.JsValue$class.validate(JsValue.scala:17)
at play.api.libs.json.JsObject.validate(JsValue.scala:124)
at play.api.libs.json.JsReadable$class.as(JsReadable.scala:23)
at play.api.libs.json.JsObject.as(JsValue.scala:124)
Maybe you will be more likely to create a MapPass class case and use Json.format to do the work for you!
import play.api.libs.json._
val a: String = """{
"pass": {
"key-1": {
"field1": "aaaa",
"field2": "aaaa"
},
"key-2": {
"field1": "aaaa",
"field2": "aaaa"
},
"key-3": {
"field1": "aaaa",
"field2": "aaaa"
}
}
}"""
case class Pass(field1: String, field2: String)
case class MapPass(pass: Map[String, Pass])
implicit val passFormat: Format[Pass] = Json.format[Pass]
implicit val mapPassFormat: Format[MapPass] = Json.format[MapPass]
val json = Json.parse(a)
val mapPassJsResult = json.validate[MapPass]
val mapPass = mapPassJsResult.get
print(mapPass.pass.mkString("\n"))
It worked like that for me:
Related
I have a dynamic json object generated with a certain format and I would like to manipulate that object to map it to another format in scala.
The problem is that the names of the fields are dynamic so "field1" and "field2" can be anything.
Is there any way to do it dynamically in scala?
The original object:
{
"field1": {
"value" "some value 1",
"description": "some test description",
...
},
"field2": {
"value" "some value 2",
"description": "some test description",
...
}
}
And I'd like to convert it to something like:
{
"field1": "some value 1",
"field2": "some value 2"
}
You can collect all keys and then check if the downField("value") exists
import io.circe._
import io.circe.literal.JsonStringContext
object CirceFieldsToMap {
def main(args: Array[String]): Unit = {
val json: Json =
json"""{
"field1": {
"foo" : "bar1",
"value" : "foobar1"
},
"field2": {
"foo" : "bar2",
"value" : "foobar2"
},
"field3": {
"foo" : "bar2"
}
}"""
implicit val decodeFoo = new Decoder[Map[String, Option[String]]] {
final def apply(c: HCursor): Decoder.Result[Map[String, Option[String]]] = {
val result = c.keys.get //// You should handle the .get properly ( if None it breaks)
.toList
.foldLeft(List.empty[(String, Option[String])]) { case (acc, key) =>
acc :+ (key, c.downField(key).downField("value").as[String].toOption)
}
Right(result.toMap)
}
}
val myField01 = json.as[Map[String, Option[String]]]
println(myField01) //Right(Map(field1 -> Some(foobar1), field2 -> Some(foobar2), field3 -> None))
}
}
How do I create a Json (Circe) looking like this:
{
"items": [{
"field1": "somevalue",
"field2": "somevalue2"
},
{
"field1": "abc",
"field2": "123abc"
}]
}
val result = Json.fromFields(List("items" -> ???))
You can do so using Circe's built in list typeclasses for encoding JSON. This code will work:
import io.circe.{Encoder, Json}
import io.circe.syntax._
case class Field(field1: String, field2: String)
object Field {
implicit val encodeFoo: Encoder[Field] = new Encoder[Field] {
final def apply(a: Field): Json = Json.obj(
("field1", Json.fromString(a.field1)),
("field2", Json.fromString(a.field2))
)
}
}
class Encoding(items: List[Field]) {
def getJson: Json = {
Json.obj(
(
"items",
items.asJson
)
)
}
}
If we instantiate an "Encoding" class and call getJson it will give you back the desired JSON. It works because with circe all you need to do to encode a list is provide an encoder for whatever is inside the list. Thus, if we provide an encoder for Field it will encode it inside a list when we call asJson on it.
If we run this:
val items = new Encoding(List(Field("jf", "fj"), Field("jfl", "fjl")))
println(items.getJson)
we get:
{
"items" : [
{
"field1" : "jf",
"field2" : "fj"
},
{
"field1" : "jfl",
"field2" : "fjl"
}
]
}
For example, here payload is optional and it has 3 variants:
How can I parse the json with types like option[either[A,B,C]] but to use abstract data type using things sealed trait or sum type?
Below is a minimal example with some boiler plate:
https://scalafiddle.io/sf/K6RUWqk/1
// Start writing your ScalaFiddle code here
val json =
"""[
{
"id": 1,
"payload" : "data"
},
{
"id": 2.1,
"payload" : {
"field1" : "field1",
"field2" : 5,
"field3" : true
}
},
{
"id": 2.2,
"payload" : {
"field1" : "field1",
}
},
{
"id": 3,
payload" : 4
},
{
"id":4,
"
}
]"""
final case class Data(field1: String, field2: Option[Int])
type Payload = Either[String, Data]
final case class Record(id: Int, payload: Option[Payload])
import io.circe.Decoder
import io.circe.generic.semiauto.deriveDecoder
implicit final val dataDecoder: Decoder[Data] = deriveDecoder
implicit final val payloadDecoder: Decoder[Payload] = Decoder[String] either Decoder[Data]
implicit final val recordDecoder: Decoder[Record] = deriveDecoder
val result = io.circe.parser.decode[List[Record]](json)
println(result)
Your code is almost fine, you have just syntax issues in your json and Record.id should be Double instead of Int - because it is how this field present in your json ("id": 2.1). Please, find fixed version below:
val json =
s"""[
{
"id": 1,
"payload" : "data"
},
{
"id": 2.1,
"payload" : {
"field1" : "field1",
"field2" : 5,
"field3" : true
}
},
{
"id": 2.2,
"payload" : {
"field1" : "field1"
}
},
{
"id": 3,
"payload" : 4
},
{
"id": 4
}
]"""
type Payload = Either[String, Data]
final case class Data(field1: String, field2: Option[Int])
final case class Record(id: Double, payload: Option[Payload]) // id is a Double in your json in some cases
import io.circe.Decoder
import io.circe.generic.semiauto.deriveDecoder
implicit val dataDecoder: Decoder[Data] = deriveDecoder
implicit val payloadDecoder: Decoder[Payload] = Decoder[String] either Decoder[Data]
implicit val recordDecoder: Decoder[Record] = deriveDecoder
val result = io.circe.parser.decode[List[Record]](json)
println(result)
Which produced in my case:
Right(List(Record(1.0,Some(Left(data))), Record(2.1,Some(Right(Data(field1,Some(5))))), Record(2.2,Some(Right(Data(field1,None)))), Record(3.0,Some(Left(4))), Record(4.0,None)))
UPDATE:
The more general approach would be to use so-called Sum Types or in simple words - general sealed trait with several different implementations. Please, see for more details next Circe doc page : https://circe.github.io/circe/codecs/adt.html
In your case it can be achieved something like this:
import cats.syntax.functor._
import io.circe.Decoder
import io.circe.generic.semiauto.deriveDecoder
sealed trait Payload
object Payload {
implicit val decoder: Decoder[Payload] = {
List[Decoder[Payload]](
Decoder[StringPayload].widen,
Decoder[IntPayload].widen,
Decoder[ObjectPayload].widen
).reduce(_ or _)
}
}
case class StringPayload(value: String) extends Payload
object StringPayload {
implicit val decoder: Decoder[StringPayload] = Decoder[String].map(StringPayload.apply)
}
case class IntPayload(value: Int) extends Payload
object IntPayload {
implicit val decoder: Decoder[IntPayload] = Decoder[Int].map(IntPayload.apply)
}
case class ObjectPayload(field1: String, field2: Option[Int]) extends Payload
object ObjectPayload {
implicit val decoder: Decoder[ObjectPayload] = deriveDecoder
}
final case class Record(id: Double, payload: Option[Payload])
object Record {
implicit val decoder: Decoder[Record] = deriveDecoder
}
def main(args: Array[String]): Unit = {
val json =
s"""[
{
"id": 1,
"payload" : "data"
},
{
"id": 2.1,
"payload" : {
"field1" : "field1",
"field2" : 5,
"field3" : true
}
},
{
"id": 2.2,
"payload" : {
"field1" : "field1"
}
},
{
"id": 3,
"payload" : "4"
},
{
"id": 4
}
]"""
val result = io.circe.parser.decode[List[Record]](json)
println(result)
}
which produced in my case next output:
Right(List(Record(1.0,Some(StringPayload(data))), Record(2.1,Some(ObjectPayload(field1,Some(5)))), Record(2.2,Some(ObjectPayload(field1,None))), Record(3.0,Some(StringPayload(4))), Record(4.0,None)))
Hope this helps!
i have a json like this
{
"value.first" : "one",
"value.second" : "two",
"value.third" : "three"
}
how do I transform it like this in Scala/Play? :
{
"value": {
"first": "one",
"second": "two",
"third": "three"
}
}
The solution depends on the necessary flexibility and the handling of wrong json format. Maybe the following will work for you.
import play.api.libs.json._
val jsonInitial = Json.obj(
"value.first" -> "one",
"value.second" -> "two",
"value.third" -> "three"
)
val primary: String = jsonInitial.keys.headOption
.map{ _.split('.')(0) }
.getOrElse("empty")
val secondary: Seq[(String, JsValue)] = jsonInitial.fields
.map{ case (k, v) => (k.split('.')(1), v) }
val jsonModified = Json.obj(
primary -> JsObject(secondary)
)
I have the following data structure:
val jsonStr = """
{
"data1": {
"field1": "data1",
"field2": 1.0,
"field3": true
},
"data211": {
"field1": "data211",
"field2": 4343.0,
"field3": false
},
"data344": {
"field1": "data344",
"field2": 436778.51,
"field3": true
},
"data41": {
"field1": "data41",
"field2": 14348.0,
"field3": true
}
}
"""
I want extract it. Here is what I'm doing without any luck:
#1.
case class Fields(field1: String, field2: Double, field3: Boolean)
json.extract[Map[String, Map[Fields, String]]]
//org.json4s.package$MappingException: Do not know how to convert JBool(true)
//into class java.lang.String
#2.
json.extract[Map[String, Map[String, Fields]]
//java.lang.InternalError: Malformed class name
#3.
json.extract[Map[String, Map[String, Any]]]
//org.json4s.package$MappingException: No information known about type
#4.
json.extract[Map[String, Map[String, String]]]
//org.json4s.package$MappingException: Do not know
//how to convert JBool(true) into class java.lang.String
How do I do that then?
P.S. -- actually, that's https://github.com/json4s/json4s but it's doesn't really matter since lift has the same API regarding json extracting.
UPDATE: It probably will require to use transform method. How will I use it?
val json = parse(jsonStr) transform {
case //.... what should be here to catch JBool -- "field3"?
}
UPDATE2:
#5
json.extract[Map[String, Map[String, JValue]]]
// Works! but it's not what I'm looking for, I need to use a pure Java/Scala type
scala> val jsonStr = """
| {
| "data1": {
| "field1": "data1",
| "field2": 1.0,
| "field3": true
| },
| "data211": {
| "field1": "data211",
| "field2": 4343.0,
| "field3": false
| },
| "data344": {
| "field1": "data344",
| "field2": 436778.51,
| "field3": true
| },
| "data41": {
| "field1": "data41",
| "field2": 14348.0,
| "field3": true
| }
| }
| """
jsonStr: java.lang.String =
"
{
"data1": {
"field1": "data1",
"field2": 1.0,
"field3": true
},
"data211": {
"field1": "data211",
"field2": 4343.0,
"field3": false
},
"data344": {
"field1": "data344",
"field2": 436778.51,
"field3": true
},
"data41": {
"field1": "data41",
"field2": 14348.0,
"field3": true
}
}
"
scala> import net.liftweb.json._
import net.liftweb.json._
scala> implicit val formats = DefaultFormats
formats: net.liftweb.json.DefaultFormats.type = net.liftweb.json.DefaultFormats$#361ee3df
scala> val json = parse(jsonStr)
json: net.liftweb.json.package.JValue = JObject(List(JField(data1,JObject(List(JField(field1,JString(data1)), JField(field2,JDouble(1.0)), JField(field3,JBool(true))))), JField(data211,JObject(List(JField(field1,JString(data211)), JField(field2,JDouble(4343.0)), JField(field3,JBool(false))))), JField(data344,JObject(List(JField(field1,JString(data344)), JField(field2,JDouble(436778.51)), JField(field3,JBool(true))))), JField(data41,JObject(List(JField(field1,JString(data41)), JField(field2,JDouble(14348.0)), JField(field3,JBool(true)))))))
scala> case class Fields(field1: String, field2: Double, field3: Boolean)
defined class Fields
scala> json.extract[Map[String, Fields]]
res1: Map[String,Fields] = Map(data1 -> Fields(data1,1.0,true), data211 -> Fields(data211,4343.0,false), data344 -> Fields(data344,436778.51,true), data41 -> Fields(data41,14348.0,true))