Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do you need to create these json read/write when in java you didn't have to?

Please correct me if I am wrong, but when using Java with say Spring MVC you didn't have to create these extra classes to map your Java class to JSON and JSON to class.

Why do you have to do this in Play with Scala? Is it something to do with Scala?

case class Location(lat: Double, long: Double)

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))


implicit val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double] and
  (JsPath \ "long").read[Double]
)(Location.apply _)
like image 995
public static Avatar asked Dec 12 '22 04:12

public static


2 Answers

The reason why you have to do it in Play is a framework design choice, and it is a very good one.

In Play, the mechanism relies on Scala implicits, which are a very powerful feature leveraged to make the mechanism highly pluggable , in the sense that at the moment you call:

Json.toJson(Location(4.5, 5.3)) 

The compiler will look for an implicit in scope matching the required type. The Scala language specification describes the algorithm to resolve implicits, and such algorithm is designed in a way that you can "import" an implicit in a limited scope. Thanks to this feature, in different part of your program you can make visible a different implementation of your Reads / Writes or any typeclass .

object MyImplicits {

    object ImplicitJson1{
        implicit val write:Write[Location] = "write to json all fields"
    }

    object ImplicitJson2{
        implicit val write:Write[Location] = "skip field a"
    }
}

object MyBusinessCode{

    def f1(location:Location){
        import MyImplicits.ImplicitJson1._
        Json.toJson(location)
    }
    def f2(location:Location){
        import MyImplicits.ImplicitJson2._
        Json.toJson(location)
    }

    def dynamicChoice(location:Location){
        implicit val write = {
            if(location.isEurope)           
                MyImplicits.ImplicitJson1.write
            else
                MyImplicits.ImplicitJson2.write
        }
        Json.toJson(location)
    }

}

Instead, in Spring this is typically done through introspection and reflection. You might need to use annotations to help Spring determining how to build your Json from your data model. The consequence is that you cannot change the way it is done, and therefore you have less flexibility.

Since you might not need more flexibility, many Scala libraries/framework provides you functions to generate default implementation of the typeclass you need. That extra line of code

implicit val fmt = Json.format[Location]

is the price that you have to pay because Play json serialization relies on implicit.

like image 148
Edmondo1984 Avatar answered Apr 13 '23 01:04

Edmondo1984


You don't need to:

case class Location(lat: Double, long: Double)

object Location {

  implicit val fmt = Json.format[Location]

}

Json.toJson(Location(4.5, 5.3)) // returns JsValue

The hand-written reads/writes/formats are useful when your JSON structure doesn't match your object definition.

like image 38
Ryan Avatar answered Apr 12 '23 23:04

Ryan