the question is not specific for JsValue, its for all the immutable objects in scala which i want to edit part of it and keep the rest as is.
for example i have this object:
"references": {
"hsId": "37395615-244b-4706-b6f5-237272f07140",
"others": {
"path": "rewr",
"externalId": "ewr",
"version": "2"
}
}
and lets say i just want to edit the version.
thanks
ok i figured out a way of solution,
but i feel its a patch and not the best answer
val references: JsObject = (json \ "references").as[JsObject]
val newVersion = JsObject(List(("others", JsObject(List(("version", JsString("3")))).as[JsValue])))
val newReferences = references.deepMerge(newVersion)
You could use JSON transformers. Let's say we want to change the version to "3".
val js: JsValue = Json.parse("""
{
"references": {
"hsId": "37395615-244b-4706-b6f5-237272f07140",
"others": {
"path": "rewr",
"externalId": "ewr",
"version": "2"
}
}
}
""")
// Define the transformer
val transformer = (__ \ "references" \ "others").json.update(
__.read[JsObject].map{o => o ++ Json.obj("version" -> "3")}
)
val newJs = js.transform(transformer)
This will copy the entire object, then replace version on the others branch.
Related
Suppose I have some JSON data like this:
{
"data": {
"title": "example input",
"someBoolean": false,
"innerData": {
"innerString": "input inner string",
"innerBoolean": true,
"innerCollection": [1,2,3,4,5]
},
"collection": [6,7,8,9,0]
}
}
And I want to flatten it a bit and transform or remove some fields, to get the following result:
{
"data": {
"ttl": "example input",
"bool": false,
"collection": [6,7,8,9,0],
"innerCollection": [1,2,3,4,5]
}
}
How can I do this with Circe?
(Note that I'm asking this as a FAQ since similar questions often come up in the Circe Gitter channel. This specific example is from a question asked there yesterday.)
I've sometimes said that Circe is primarily a library for encoding and decoding JSON, not for transforming JSON values, and in general I'd recommend mapping to Scala types and then defining relationships between those (as Andriy Plokhotnyuk suggests here), but for many cases writing transformations with cursors works just fine, and in my view this kind of thing is one of them.
Here's how I'd implement this transformation:
import io.circe.{DecodingFailure, Json, JsonObject}
import io.circe.syntax._
def transform(in: Json): Either[DecodingFailure, Json] = {
val someBoolean = in.hcursor.downField("data").downField("someBoolean")
val innerData = someBoolean.delete.downField("innerData")
for {
boolean <- someBoolean.as[Json]
collection <- innerData.get[Json]("innerCollection")
obj <- innerData.delete.up.as[JsonObject]
} yield Json.fromJsonObject(
obj.add("boolean", boolean).add("collection", collection)
)
}
And then:
val Right(json) = io.circe.jawn.parse(
"""{
"data": {
"title": "example input",
"someBoolean": false,
"innerData": {
"innerString": "input inner string",
"innerBoolean": true,
"innerCollection": [1,2,3]
},
"collection": [6,7,8]
}
}"""
)
And:
scala> transform(json)
res1: Either[io.circe.DecodingFailure,io.circe.Json] =
Right({
"data" : {
"title" : "example input",
"collection" : [
6,
7,
8
]
},
"boolean" : false,
"collection" : [
1,
2,
3
]
})
If you look at it the right way, our transform method kind of resembles a decoder, and we can actually write it as one (although I'd definitely recommend not making it implicit):
import io.circe.{Decoder, Json, JsonObject}
import io.circe.syntax._
val transformData: Decoder[Json] = { c =>
val someBoolean = c.downField("data").downField("someBoolean")
val innerData = someBoolean.delete.downField("innerData")
(
innerData.delete.up.as[JsonObject],
someBoolean.as[Json],
innerData.get[Json]("innerCollection")
).mapN(_.add("boolean", _).add("collection", _)).map(Json.fromJsonObject)
}
This can be convenient in some situations where you want to perform the transformation as part of a pipeline that expects a decoder:
scala> io.circe.jawn.decode(myJsonString)(transformData)
res2: Either[io.circe.Error,io.circe.Json] =
Right({
"data" : {
"title" : "example input",
"collection" : [ ...
This is also potentially confusing, though, and I've thought about adding some kind of Transformation type to Circe that would encapsulate transformations like this without questionably repurposing the Decoder type class.
One nice thing about both the transform method and this decoder is that if the input data doesn't have the expected shape, the resulting error will include a history that points to the problem.
Suppose I want to decode some values from a JSON array into a case class with circe. The following works just fine:
scala> import io.circe.generic.auto._, io.circe.jawn.decode
import io.circe.generic.auto._
import io.circe.jawn.decode
scala> case class Foo(name: String)
defined class Foo
scala> val goodDoc = """[{ "name": "abc" }, { "name": "xyz" }]"""
goodDoc: String = [{ "name": "abc" }, { "name": "xyz" }]
scala> decode[List[Foo]](goodDoc)
res0: Either[io.circe.Error,List[Foo]] = Right(List(Foo(abc), Foo(xyz)))
It's sometimes the case that the JSON array I'm decoding contains other, non-Foo-shaped stuff, though, which results in a decoding error:
scala> val badDoc =
| """[{ "name": "abc" }, { "id": 1 }, true, "garbage", { "name": "xyz" }]"""
badDoc: String = [{ "name": "abc" }, { "id": 1 }, true, "garbage", { "name": "xyz" }]
scala> decode[List[Foo]](badDoc)
res1: Either[io.circe.Error,List[Foo]] = Left(DecodingFailure(Attempt to decode value on failed cursor, List(DownField(name), MoveRight, DownArray)))
How can I write a decoder that ignores anything in the array that can't be decoded into my case class?
The most straightforward way to solve this problem is to use a decoder that first tries to decode each value as a Foo, and then falls back to the identity decoder if the Foo decoder fails. The new either method in circe 0.9 makes the generic version of this practically a one-liner:
import io.circe.{ Decoder, Json }
def decodeListTolerantly[A: Decoder]: Decoder[List[A]] =
Decoder.decodeList(Decoder[A].either(Decoder[Json])).map(
_.flatMap(_.left.toOption)
)
It works like this:
scala> val myTolerantFooDecoder = decodeListTolerantly[Foo]
myTolerantFooDecoder: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon$21#2b48626b
scala> decode(badDoc)(myTolerantFooDecoder)
res2: Either[io.circe.Error,List[Foo]] = Right(List(Foo(abc), Foo(xyz)))
To break down the steps:
Decoder.decodeList says "define a list decoder that tries to use the given decoder to decode each JSON array value".
Decoder[A].either(Decoder[Json] says "first try to decode the value as an A, and if that fails decode it as a Json value (which will always succeed), and return the result (if any) as a Either[A, Json]".
.map(_.flatMap(_.left.toOption)) says "take the resulting list of Either[A, Json] values and remove all the Rights".
…which does what we want in a fairly concise, compositional way. At some point we might want to bundle this up into a utility method in circe itself, but for now writing out this explicit version isn't too bad.
I'm using Scala Play framework and Instagram API, and I want to extract a json array to my model class User:
case class User(val userId: String, val username: String, val profilePhoto: String, val name: String)
An json array example from the API is something like this:
{
"pagination": {},
"meta": {},
"data": [
{
"username": "carolinabentocb",
"profile_picture": "https://igcdn-photos-f-a.akamaihd.net/hphotos-ak-xfa1/t51.2885-19/s150x150/11429783_1673078532912085_1496721162_a.jpg",
"id": "363753337",
"full_name": "Carolina Bento"
},
{
"username": "pereira3044",
"profile_picture": "https://igcdn-photos-e-a.akamaihd.net/hphotos-ak-xaf1/t51.2885-19/s150x150/11351764_1662987433917180_971708049_a.jpg",
"id": "2141448590",
"full_name": "Alex"
}
]
}
In this link it is explained on how to map a json object to a model class, but how can I map the json array to a Seq/List/Array of Users?
The Json inception code is really great and it my preferred way to deserialize json. You will have to modify your User class to fit the instagram model API. Alternatively you could make a case class like InstagramApiUser or something to do the deserialization and copy to your own class later if you decide that is better for your flow. Here is the code and it works in a scala repl.
import play.api.libs.json.{Json, Format}
val js = Json.parse("""{
"pagination": {},
"meta": {},
"data": [
{
"username": "carolinabentocb",
"profile_picture": "https://igcdn-photos-f-a.akamaihd.net/hphotos-ak-xfa1/t51.2885-19/s150x150/11429783_1673078532912085_1496721162_a.jpg",
"id": "363753337",
"full_name": "Carolina Bento"
},
{
"username": "pereira3044",
"profile_picture": "https://igcdn-photos-e-a.akamaihd.net/hphotos-ak-xaf1/t51.2885-19/s150x150/11351764_1662987433917180_971708049_a.jpg",
"id": "2141448590",
"full_name": "Alex"
}
]
}""")
case class User(id: String, username: String, profile_picture: String, full_name: String)
object User {
implicit val jsonFormat: Format[User] = Json.format[User]
}
val result = (js \ "data").as[Seq[User]]
There are three methods to deserialize Json in the Play Json library, and as is the least idiomatic one in my opinion as it throws an exception if it fails to parse. You could try using asOpt[A] which will produce an Option[A] or better validate[A] which will produce a JsResult[A] and then you can log an error with the reason(s) that parsing your Json failed.
If you don't like naming your case class members to match the API names you can write the Reads manually like
import play.api.libs.json.{Json, Reads, JsPath}
import play.api.libs.functional.syntax._
case class User(val userId: String, val username: String, val profilePhoto: String, val name: String)
object User {
implicit val jsonReads: Reads[User] = (
(JsPath \ "id").read[String] and
(JsPath \ "username").read[String] and
(JsPath \ "profile_picture").read[String] and
(JsPath \ "full_name").read[String]
)(User.apply _)
}
And it works the same way otherwise.
The solution I've found is the following:
val users: Seq[User] = (json \ "data").as[JsArray].value.map(j => j.validate[User].get)
It may exist a more beautiful approach, but I will stick with this one until other answers.
I'm becoming a fan of the play-json coast-to-coast pattern, especially its use of combinators. I have some complex cases I'm munging together. I know there's a better way, but I'm new to the combinator approach to build up functionality.
I'd like to turn this:
{
"somearray": [
{
"field1": "value1",
"field2": "value1",
"key": "key1"
},
{
"field1": "second1",
"field2": "second1",
"key": "key2"
}
]
}
into this:
{
"someObj": {
"key1":
{
"field1": "value1",
"field2": "value1"
},
"key2":
{
"field1": "second1",
"field2": "second1"
}
]
}
I can get this to work, but I exit out of the transformation:
(__ \ "someObj").json.copyFrom(__ \ "someArray".json.pick.map {
case JsArray(arr) => {
JsObject(arr.map(a =>
a.transform(<A transform function to prune key>).map(pruned => {
((a \ "key").as[String], pruned)
}).flatMap({
case JsSuccess(result, _) => Seq(result)
case other => Nil
})
}
case other => JsNull
})
THere are some issues with this code: I know it's verbose, I know I'm assume "key" is present with a string type, and I need that flatMap to get me out of a JsResult and into some JsValue I can use to build the JsObject.
It seems like I should be able to
1) Create a sub transformation, i.e. a.transform() can be nestled in the parent transform without unpacking and repacking the json object.
Thank you.
How about this (ignoring the fact that this code still has the same issues you already mentioned)?
def elementTransform = for {
keyValue <- (__ \ 'key).json.pick // picks value for key field
obj <- (__ \ 'key).json.prune // returns object without the key field
res <- (__ \ keyValue.as[String]).json.put(obj) // uses the keyValue as a field name
} yield res
val transform = (__ \ "someObj").json.copyFrom((__ \ "someArray").json.pick[JsArray].map(
_.value.foldLeft[JsResult[JsObject]](JsSuccess(Json.obj()))(
(obj, a) =>
for {
o <- obj
field <- a.transform(elementTransform)
} yield o ++ field
).fold(_ => JsNull, identity)
))
How do I read multiple json objects from a request and convert them into my own custom object. For example, lets say we are retrieving a list of users with the following json:
{
"users":[
{
"name": "Bob",
"age": 31.0,
"email": "bob#gmail.com"
},
{
"name": "Kiki",
"age": 25.0,
"email": null
}
]
}
and my case class looks like the following
case class User(firstName: String, age: Double, email: String)
In this case the firstName value is differnt from the "name" json value. How do I get a Seq[User] from the given json file with the different names. I can't find any examples where someone is reading from a json file with multiple objects.
Thanks in advance.
Play's type class-based JSON library provides reader and writer instances for Seq[A] for any A with the appropriate instances, so all you have to do is provide a reader instance for your user type and you get a reader for a collection of users for free.
We can start with the following case class (note that I've made the fact that the email address is optional explicit with Option):
case class User(firstName: String, age: Double, email: Option[String])
Next we define our reader. I'm using 2.1's combinators, but there are other (more verbose) options.
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val userReads = (
(__ \ 'name).read[String] and
(__ \ 'age).read[Double] and
(__ \ 'email).read[Option[String]]
)(User.apply _)
We can test it:
val userJson = """{
"users": [
{ "name": "Bob", "age": 31.0, "email": "bob#gmail.com" },
{ "name": "Kiki", "age": 25.0, "email": null }
]
}"""
val users = (Json.parse(userJson) \ "users").as[Seq[User]]
And then:
scala> users foreach println
User(Bob,31.0,Some(bob#gmail.com))
User(Kiki,25.0,None)
As expected.