问题
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?
回答1:
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")
}
回答2:
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.
回答3:
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.
回答4:
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
来源:https://stackoverflow.com/questions/15915266/how-to-fill-case-class-from-json-with-partial-data