Looking at this helpful answer on encoding a List[A] in the Play JSON library,
I tried to use Json#arr:
import play.api.libs.json._
scala> Json.arr( 1, 2, 3 )
res21: play.api.libs.json.JsArray = [1,2,3]
All works well so far, but what if I have a List[Int]:
scala> Json.arr( List(1,2,3) )
res22: play.api.libs.json.JsArray = [[1,2,3]] // not what I wanted
I attempted to pass the List[Int] as var-args (if that's the right term):
scala> Json.arr( List(1,2,3): _* )
<console>:18: error: type mismatch;
found : List[Int]
required: Seq[play.api.libs.json.Json.JsValueWrapper]
Json.arr( List(1,2,3): _* )
^
But that did not work.
Then, I tried to create a List[JsValueWrapper], and then pass that, via var-args, to Json#arr:
scala> List(1,2,3).map(Json.toJsFieldJsValueWrapper)
<console>:18: error: No Json serializer found for type T. Try to implement an implicit Writes or Format for this type.
List(1,2,3).map(Json.toJsFieldJsValueWrapper)
^
Although that failed, the following works when applied to a single Int:
scala> Json.toJsFieldJsValueWrapper(1)
res27: play.api.libs.json.Json.JsValueWrapper = JsValueWrapperImpl(1)
scala> Json.arr(res27)
res28: play.api.libs.json.JsArray = [1]
How can I pass a List[Int] into Json.arr to get an output of [1,2,3], i.e. a JsArray consisting of three JsNumber's?
You could use Json.toJson, validate as a JsArray, or return an empty one if invalid:
scala> val list = List(1, 2, 3)
list: List[Int] = List(1, 2, 3)
scala> Json.toJson(list).validate[JsArray].getOrElse(JsArray())
res6: play.api.libs.json.JsArray = [1,2,3]
You could also modify your lambda slightly with Json.arr:
scala> Json.arr(list.map(Json.toJsFieldJsValueWrapper(_)): _*)
res10: play.api.libs.json.JsArray = [1,2,3]
It seems as though without the parentheses, the compiler is having trouble inferring what T is. This is because the compiler tries to resolve the implicit before eta-expanding the anonymous function, so it doesn't know what argument will be passed to Json.toJsFieldJsValueWrapper yet. Json.toJsFieldJsValueWrapper(_) is fundamentally different because it expands to a slightly different Function1:
x => Json.toJsFieldJsValueWrapper(x)
Here the compiler knows that x will be an Int, so the implicit Writes[Int] is resolved.
How about just using Json.toJson and casting to a JsArray (if you need to)?
scala> Json.toJson(List(1,2,3)).as[JsArray]
res0: play.api.libs.json.JsArray = [1,2,3]
Related
This question has come up a few times recently, so I'm FAQ-ing it here. Suppose I've got some case classes like this:
import io.circe._, io.circe.generic.semiauto._
object model {
case class A(a: String)
case class B(a: String, i: Int)
case class C(i: Int, b: Boolean)
implicit val encodeA: Encoder[A] = deriveEncoder
implicit val encodeB: Encoder[B] = deriveEncoder
implicit val encodeC: Encoder[C] = deriveEncoder
implicit val decodeA: Decoder[A] = deriveDecoder
implicit val decodeB: Decoder[B] = deriveDecoder
implicit val decodeC: Decoder[C] = deriveDecoder
}
And I want to encode a value that could be any one of these as JSON using circe and Shapeless coproducts.
import io.circe.shapes._, io.circe.syntax._
import shapeless._
import model._
type ABC = A :+: B :+: C :+: CNil
val c: ABC = Coproduct[ABC](C(123, false))
This looks fine at first:
scala> c.asJson
res0: io.circe.Json =
{
"i" : 123,
"b" : false
}
But the problem is that I can never decode a coproduct containing a B element, since any valid JSON document that could be decoded as B can also be decoded as A, and the coproduct decoders provided by circe-shapes try the elements in the order they appear in the coproduct.
scala> val b: ABC = Coproduct[ABC](B("xyz", 123))
b: ABC = Inr(Inl(B(xyz,123)))
scala> val json = b.asJson
json: io.circe.Json =
{
"a" : "xyz",
"i" : 123
}
scala> io.circe.jawn.decode[ABC](json.noSpaces)
res1: Either[io.circe.Error,ABC] = Right(Inl(A(xyz)))
How can I disambiguate the elements of my coproduct in my encoding?
The circe-shapes module encodes ordinary hlists and coproducts without labels, as seen above: a coproduct as the bare JSON representation of the element, and an hlist ends up as just a JSON array (the same as the default tuple encoding):
scala> ("xyz" :: List(1, 2, 3) :: false :: HNil).asJson.noSpaces
res2: String = ["xyz",[1,2,3],false]
In the case of hlists there's no danger of ambiguity because of overlapping names, but for coproducts there is. In both cases, though, you can add labels to the JSON representation using Shapeless's labeling mechanism, which tags values with a type-level symbol.
An hlist with labels is called a "record", and a coproduct with labels is a "union". Shapeless provides special syntax and operations for both of these, and circe-shapes treats both differently from unlabeled hlists or coproducts. For example (assuming the definitions and imports above):
scala> import shapeless.union._, shapeless.syntax.singleton._
import shapeless.union._
import shapeless.syntax.singleton._
scala> type ABCL = Union.`'A -> A, 'B -> B, 'C -> C`.T
defined type alias ABCL
scala> val bL: ABCL = Coproduct[ABCL]('B ->> B("xyz", 123))
bL: ABCL = Inr(Inl(B(xyz,123)))
scala> val jsonL = bL.asJson
jsonL: io.circe.Json =
{
"B" : {
"a" : "xyz",
"i" : 123
}
}
scala> io.circe.jawn.decode[ABCL](jsonL.noSpaces)
res3: Either[io.circe.Error,ABCL] = Right(Inr(Inl(B(xyz,123))))
The encoding for records similarly includes the member names:
scala> ('a ->> "xyz" :: 'b ->> List(1) :: 'c ->> false :: HNil).asJson.noSpaces
res4: String = {"c":false,"b":[1],"a":"xyz"}
In general if you want your hlist and coproduct encodings to include labels (and look like the encodings you'd get from circe-generic for case classes and sealed trait hierarchies), you can use records or coproducts to tell circe-shapes to do this with a minimal amount of extra syntactic and runtime overhead.
Using Scala and json4s (Maybe Im missing a golden fish library or something)
I am trying to add some list (or array) of strings to a JSON so in the end looks like:
{"already":"here",..."listToAdd":["a","b",c"]}
The fact is that I already have the String in a JObject and the list of strings in an Array[String] (but it could be changed to List if needed). So I followed the docat json4s.org which states that:
Any seq produces JSON array.
scala> val json = List(1, 2, 3)
scala> compact(render(json))
res0: String = [1,2,3]
Tuple2[String, A] produces field.
scala> val json = ("name" -> "joe")
scala> compact(render(json))
res1: String = {"name":"joe"}
And when trying it, it gives:
Error:(15, 28) type mismatch;
found : (String, String)
required: org.json4s.JValue
which expands to) org.json4s.JsonAST.JValue
println(compact(render(idJSON)))
Using Scala 2.11.4
Json4s 3.2.11 (Jackson)
You have to additionally import some implicit conversion methods:
import org.json4s.JsonDSL._
These will convert the Scala objects into the library's Json AST.
I have a case class defined this way:
scala> import play.api.libs.json._
import play.api.libs.json._
scala> import play.api.libs.functional.syntax._
import play.api.libs.functional.syntax._
scala> type MapIntString = Map[Int, String]
defined type alias MapIntString
case class Duh(a: MapIntString)
defined class Duh
When I tried declaring its Writes this way, I got an error:
scala> implicit val duhWrites = Json.writes[Duh]
<console>:25: error: No implicit Writes for MapIntString available.
implicit val duhWrites = Json.writes[Duh]
So I resort to this..., still got an error:
scala> implicit val duhWrites: Writes[Duh] = (
| (JsPath \ "a").write[MapIntString]
| )(unlift(Duh.unapply _))
<console>:26: error: overloaded method value write with alternatives:
(t: MapIntString)(implicit w: play.api.libs.json.Writes[MapIntString])play.api.libs.json.OWrites[play.api.libs.json.JsValue] <and>
(implicit w: play.api.libs.json.Writes[MapIntString])play.api.libs.json.OWrites[MapIntString]
cannot be applied to (Duh => MapIntString)
(JsPath \ "a").write[MapIntString]
Can somebody help me pointing out where I did wrong?
I feel that my understanding of this reads/writes combinators a bit shaky. I posted related question here (when I was trying to understand what was going on): What is this "and" in ScalaJsonCombinator (when defining a Writes)?
If you know the best way to implement the Writes and Reads for "Duh" please share.
Thanks in advance!
There are no predefined Writes for Map[Int, String], only Map[String, A]. This is because all JSON keys must be a String, to Int keys don't really make any sense. You can fix this by defining one that converts the Int keys to Strings.
type MapIntString = Map[Int, String]
implicit val mapIntWrites = new Writes[MapIntString] {
def writes(m: MapIntString): JsValue =
Json.toJson(m.map { case (k, v) => (k.toString, v)} )
}
scala> implicit val duhWrites = Json.writes[Duh]
duhWrites: play.api.libs.json.OWrites[Duh] = play.api.libs.json.OWrites$$anon$2#50973f47
I've been using this json combinator for several basic / standard cases without really understanding how it works. All was fine.
Now I want to get myself prepared for whatever advanced cases might come; I need to understand the code.
Ref.: https://www.playframework.com/documentation/2.3.x/ScalaJsonCombinators
I think I can understand the Reads:
implicit val locationReads: Reads[Location] = (
(JsPath \ "lat").read[Double] and
(JsPath \ "long").read[Double]
)(Location.apply _)
It creates a Reads that:
First -- when given a JsValue (through its "reads" method) -- it pulls the "lat", followed by the "long". Out of those two it creates a tuple (Double, Double). -- https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.libs.json.Reads
That tuple is then assigned to the partial function of that Reads..., which in this case is whatever returned by "Location.apply _". I tried it in repl:
...
scala> val yowMan = Location.apply _
yowMan: (Double, Double) => Location = <function2>
scala> yowMan(1, 2)
res14: Location = Location(1.0,2.0)
That partial function takes the a tuple of (Double, Double) as input. So..., the outcome of step 1 is channeled to step 2, and we get an instance of Location as the return of "reads".
Now for the Writes:
implicit val locationWrites: Writes[Location] = (
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
)(unlift(Location.unapply))
First the "unapply". I tried in repl:
scala> val heyDude = Location.unapply
<console>:16: error: missing arguments for method unapply in object Location;
follow this method with `_' if you want to treat it as a partially applied function
val heyDude = Location.unapply
Oops, ok, I followed the instruction:
scala> val heyDude = Location.unapply _
heyDude: Location => Option[(Double, Double)] = <function1>
Ok, so we get a partial function that transforms an instance of Location to an (optional) tuple of (Double, Double).
Next, the "unlift":
scala> val hohoho = unlift(heyDude)
hohoho: Location => (Double, Double) = <function1>
scala> val loc = Location(1, 2)
loc: Location = Location(1.0,2.0)
scala> hohoho(loc)
res16: (Double, Double) = (1.0,2.0)
Ok, so... unlift simply throws away the "Option", and takes us directly to the tuple.
Ok... so... I guess... this "writes" of the Writes... *) when given an instance of Location, it will:
Pass that object through that partial function produced by unlift(Location.unapply).
The tuple (Double, Double) returned by that partial function is then channeled to whatever is produced by this:
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
What exactly is that "whatever"? Following the API doc of JsPath, I think it is OWrites: https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.libs.json.OWrites
But... I can't see there's a method named "and" in OWrites. Where is this "and" declared? And what does it do? Is it: "oWrites1 and oWrites2" produces "oWrites3"? And this "oWrites3" is a special type of OWrites that takes tuple as input? ... If that's the case... the tuple doesn't have information about the name of the property in the case class ("lat" and "long"). How does it know that the produced json string should be {"lat": 1, "long": 2} then?
Sorry for the train of questions. Please help me obtaining a clear understanding of this. Thanks!
*) https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.libs.json.Writes
UPDATES:
Adding related question: Syntax and meaning of a Scala/Play! code sample
When in doubt, decompile it. This showed that there is an implicit toFunctionalBuilderOps, which then you can see in FunctionalBuilderOps that there is your and method
Using an existing JsValue, I'm parsing its values to fill out a new JSON object.
val json: JsValue = getJson(...)
val newJson: JsObject = Json.obj(
"Name" -> (json \ "personName").asOpt[String].getOrElse("")
)
However, I get the following compile-time error on the third line:
No Json deserializer found for type Object.
Looking at getOrElse's docs, why does "Object," rather than "String," get returned in my above code?
final def getOrElse[B >: A](default: ⇒ B): B
I admit that I don't understand B >: A.