rvange
rvange

Reputation: 2572

How can I serialize val element in Json

When serializing the following case class, the val element is not included. Why is that, and can I have it included?

case class Asset(id: Option[Int], description: Option[String]= None) {
  val url = "images/" + id.toString+".png"
}

Update: Added Json library, and specification/intended use of the url "property".

I am using the Json library shipped with Play 2.1/Scala 2.10.

Actually the url property is meant to be a function which will look up the transformation algorithm according to configuration, for example the image can be available locally, or available from an external host.

Upvotes: 3

Views: 193

Answers (1)

Régis Jean-Gilles
Régis Jean-Gilles

Reputation: 32719

Although you sould really specify which json serialization library you are using, it is pretty much guaranteed that just doing the following will work:

case class Asset(id: Option[Int], description: Option[String]= None, url = "images/" + id.toString+".png")

Given that url has a default value anyway, turning it into a parameter won't negatively affect your code (you can still do Asset(None) by example, as before). The only downside is that now client code might create an Asset instance with a different value for url, which might not be what you want.

If that's the case, you'll probably need to create a custom json format for your Asset class, but without knowing which serialization library you use I can't help much more in this respect.


UPDATE:

Ooops, I totally missed the fact that the default value for url depends on another parameter (id) (thanks to @Kristian Domagala for noticing). Thus my above snippet does not compile. One simple solution is to put url in a second parameter list as suggested by @Kristian Domagala:

case class Asset(id:Option[Int],description:Option[String]=None)(val url:String = "images/" + id.toString+".png")

But this might not be ideal as this changed the equality semantics of Asset (url is not taken into account anymore when comparing instances), and also changes the construction syntax : when explicitly specifying the url value, you'd need to do something like Asset(Some(123))("gfx/myImage.png") instead of by example Asset(Some(123), url="gfx/myImage.png"). If you can live with these downsides this is certainly the easiest solution.

Otherwise, there is another work around: we can redefine Asset.apply ourselve (by hand):

case class AssetImpl( val id: Option[Int], val description: Option[String], val url: Option[String]) {
  override def productPrefix = "Asset"
}
type Asset = AssetImpl
object Asset {
  def apply( id: Option[Int], description: Option[String] = None, url: Option[String] = None ) = {
    new Asset( id, description, url.orElse( id.map( "images/" + _ + ".png") ) )
  }
}

As you can see I have turned url into an Option with a default value of None (avoiding the previous compilation error, as it does not depend on id anymore) due to , and in def apply... I instantiate Asset with a default value for url (this is where I actually get the value of id) of id.map( "images/" + _ + ".png").

The rest is basically just noise to be able to redefine Asset.apply: it turns out that in fact you cannot redefine the factory of a case class (you can only add separate overloads). So I renamed the class as AssetImpl, added a type alias so that noone notices ( ;-) ), and created my very own object Asset where I define the apply method (which does not conflict anymore with the auto-generated apply method because this one is in the distinct AssetImpl object. I could also simply have turned Asset into a standard class (non case class) but then I would have to redefine equals and hashCode which I find more annoying given that it must be maintained when adding/removing fields to the class.

Upvotes: 2

Related Questions