How to fill case class from json with partial data?

本秂侑毒 提交于 2019-12-22 04:58:41

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!