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?
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")
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With