7
import net.liftweb.json._
import net.liftweb.json.JsonParser._

object test02 extends App {
    implicit val formats = DefaultFormats
    case class User(
        id: Int = 0,
        name: String = "John Doe",
        gender: String = "M")

    val s1=""" {"id":1,"name":"Bill","gender":"M"} """
    var r1=Serialization.read[User](s1)
    println(r1)

    val s2=""" {"id":1} """
    var r2=Serialization.read[User](s2)
    println(r2)  

}

Second Serialization.read causes exception: net.liftweb.json.MappingException: No usable value for name.

How could I possibly read data form json into case class, but if some fields are missing they are replaced with default values from case class?

codez
  • 1,381
  • 1
  • 18
  • 28

4 Answers4

6

Looks like there's been an open ticket for this for quite a while: https://www.assembla.com/spaces/liftweb/tickets/534

In the meantime, one option is to use an Option:

    case class User(
        id: Int = 0,
        name: Option[String],
        gender: Option[String]) {
      def defaults = copy(
        name   = name   orElse Some("John Doe"),
        gender = gender orElse Some("M"))
    }
    // ...        
    val s2=""" {"id":1} """
    var r2=Serialization.read[User](s2)
    println(r2)  

That should give you:

 User(1,None,None)

And you could use something like this to fill-in default values:

 val r2 = Serialization.read[User](s2).defaults

 // r2: User = User(1,Some(John Doe),Some(M))

The other option is to use additional constructors for your case class:

 case class User(id: Int, name: String, gender: String)
 object User {
   def apply(id:Int): User = User(id, "John Doe", "M")
 }
Faiz
  • 16,025
  • 5
  • 48
  • 37
5

How to do it with the play json library, although, you have to provide the defaults in the parser and not only as default for the values:

case class User(id: Int, name: String, gender: String)

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val userReads: Reads[User] = (
  (__ \ "id").read[Int] and
  (__ \ "name").read[String].or(Reads.pure("John Doe")) and
  (__ \ "gender").read[String].or(Reads.pure("Male"))
)(User)

Json.fromJson[User](Json.parse("""{"id":1,"name":"Bill","gender":"M"}"""))

Json.fromJson[User](Json.parse("""{"id":1}"""))

I guess you could provide the defaults from the default parameter by creating a template instance of User and then passing the default from each field to Reads.pure instead of hardcoding a string there.

johanandren
  • 11,249
  • 1
  • 25
  • 30
4

Here's a Play solution that doesn't require you to specify the defaults twice or in some weird place—it uses a macro to find the appropriate default at compile-time.

First for the case class:

case class User(id: Int = 0, name: String = "John Doe", gender: String = "M")

Next you need to define DefaultFinder as I describe in this blog post. Then you're practically done:

import DefaultFinder._

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val userReads: Reads[User] = (
  (__ \ 'id).readNullable[Int] and
  (__ \ 'name).readNullable[String] and
  (__ \ 'gender).readNullable[String]
)((id, name, gender) => new User(
  id = if (id.isEmpty) default else id.get,
  name = if (name.isEmpty) default else name.get,
  gender = if (gender.isEmpty) default else gender.get
))

And finally:

scala> Json.fromJson[User](Json.parse("""{ "id": 1, "name": "Foo McBar" }"""))
res0: play.api.libs.json.JsResult[User] = JsSuccess(User(1,Foo McBar,M),)

scala> Json.fromJson[User](Json.parse("""{ "id": 2, "gender": "X" }"""))
res1: play.api.libs.json.JsResult[User] = JsSuccess(User(2,John Doe,X),)

Note that I'm not using getOrElse because the macro doesn't support it, but it could easily be made more general—it was just a quick proof-of-concept.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • 2
    No comment on the idea of `"M"` as default gender, by the way. – Travis Brown Jul 05 '14 at 17:08
  • It would be nice, if macro could generate whole read not just single default values, but for now it's best solution. – codez Jul 09 '14 at 10:00
  • @codez: you could write a macro that would (that's what Play's Inception does, although it doesn't provide this functionality). The nice thing about this approach is that it's much more general—you can use `default` in this manner with any JSON library. – Travis Brown Jul 09 '14 at 11:10
0

Most scala JSON libraries choke on this.

We use an in-house JSON library that uses macros to provide sane defaults for missing fields. (also distinguishes between Null as none, and Undefined as default. So you could have an option with a default of Some if you wanted)

Hoping to opensource it soon.

EDIT: basically, i dont know of any others that do this. but its an ever-changing ecosystem, curious to see if anybody else has tackled it yet

Colin Godsey
  • 1,293
  • 9
  • 14