2

I'd like to implement a Writes that emits a JSON object that's not found in the class being serialized.

For case class:

case class Foo(i:Int, s:String)

I'm looking to produce:

{
  "i": <int>,
  "s": "<string>",
  "other": "Some value."
}

The naïve first attempt was:

val writes: Writes[Foo] = ((
  (__ \ "i").write[Int] and
    (__ \ "s").write[String] and
    (__ \ "other").write("Some value.")
  )(unlift(Foo.unapply))

Naturally, that won't compile as the subsequent and calls produce a CanBuild3 and Foo's unapply produces a Tuple2. I'd looked into appending a value to the result, producing a Tuple3, but what I've found looks pretty bad and the language maintainers will not implement it.

There are ways to work-around this, but I'd rather not pollute my model classes with these decorator values I'd like added to the resulting JSON.

Any suggestions?

It's worth noting you can go the other direction, providing values with Reads.pure for cases where a value does not exist in JSON but is specified by the resulting object.

Community
  • 1
  • 1
Michael Ahlers
  • 616
  • 7
  • 21

2 Answers2

3

You can do this pretty straightforwardly by desugaring a bit:

val writes: Writes[Foo] = (
  (__ \ "i").write[Int] and
  (__ \ "s").write[String] and
  (__ \ "other").write[String]
)(foo => (foo.i, foo.s, "Some value."))

The unlift(Foo.unapply) is just a fancy way to get a function from a Foo to a tuple of the kind required by the preceding applicative builder expression, and you can replace it with your own function that can add whatever you want.

If you really wanted even cleaner syntax, you could use Shapeless:

import shapeless.syntax.std.tuple._

val writes: Writes[Foo] = (
  (__ \ "i").write[Int] and
  (__ \ "s").write[String] and
  (__ \ "other").write[String]
)(_ :+ "Some value.")

It's beautiful, but may be overkill.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Good suggestions! The first was a thought, but it becomes more difficult to use with higher arity products. Oh well. If the Shapeless syntax is that easy—and the library's good for production—then I'm going to try messing around with it. Thanks for your help! – Michael Ahlers Apr 11 '14 at 18:47
1

Another option is to use an object builder that implements an unapply that returns the extra values. This makes the Writes cleaner, but adds a new object. I have found this useful though as both the apply and unapply method can be used for extra massaging of data into the final object (eg: https://stackoverflow.com/a/22504468/1085606)

Example:

case class Foo(i: Int, s: String)

object FooBuilder {
  def unapply(foo: Foo): Option[(Int, String, String)] = {
    Some((foo.i, foo.s, "Some extra value"))
  }
}

val writes: Writes[Foo] = ((
  (__ \ "i").write[Int] and
    (__ \ "s").write[String] and
    (__ \ "other").write[String]
  )(unlift(FooBuilder.unapply))
Community
  • 1
  • 1
healsjnr
  • 405
  • 3
  • 10