2013-04-10 12 views
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) 

} 

Secondo Serialization.read fa eccezione: net.liftweb.json.MappingException: nessun valore utilizzabile per il nome.Come compilare case class da JSON con dati parziali?

Come potrei leggere i dati di json nella classe case, ma se mancano alcuni campi vengono sostituiti con i valori predefiniti dalla classe case?

+0

La risposta a come fare questo con la libreria json di gioco è anche accettabile. – codez

risposta

5

Sembra che ci sia stato un biglietto aperto per questo per un po 'di tempo: https://www.assembla.com/spaces/liftweb/tickets/534

Nel frattempo, una possibilità è quella di utilizzare un 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) 

Questo dovrebbe darvi:

User(1,None,None) 

E si potrebbe usare qualcosa di simile per compilare i valori di default:

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

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

L'altra opzione è quella di utilizzare i costruttori aggiuntivi per la classe caso:

case class User(id: Int, name: String, gender: String) 
object User { 
    def apply(id:Int): User = User(id, "John Doe", "M") 
} 
0

maggior parte delle librerie Scala JSON soffocare su questo.

Utilizziamo una libreria JSON interna che utilizza le macro per fornire valori predefiniti per i campi mancanti. (Distingue anche tra Null come nessuno, e non definito di default. Così si potrebbe avere un'opzione con un valore predefinito di alcuni se si voleva)

Sperando di OpenSource presto.

EDIT: in pratica, non so di nessun altro che faccia questo. ma è un ecosistema in continua evoluzione, curioso di vedere se qualcun altro lo ha ancora affrontato

5

Come farlo con la libreria json di gioco, anche se, devi fornire i valori predefiniti nel parser e non solo come predefinito per il valori:

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}""")) 

immagino che si potrebbe fornire le impostazioni di default del parametro predefinito mediante la creazione di un'istanza di modello di utente e poi passando il valore di default di ogni campo per Reads.pure invece di hardcoding una stringa lì.

4

Ecco una soluzione di gioco che non richiede di specificare i valori predefiniti due volte o in qualche luogo strano: utilizza una macro per trovare l'impostazione predefinita appropriata in fase di compilazione.

prima per la classe caso:

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

Dopodiché è necessario definire DefaultFinder come ho descritto in this blog post. Allora stai praticamente finito:

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 
)) 

E infine:

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),) 

Si noti che non sto usando getOrElse perché la macro non lo supporta, ma potrebbe facilmente essere reso più generale- era solo una rapida dimostrazione di concetto.

+2

Nessun commento sull'idea di '" M "' come genere predefinito, a proposito. –

+0

Sarebbe bello se la macro fosse in grado di generare una lettura completa, non solo singoli valori predefiniti, ma per ora è la soluzione migliore. – codez

+0

@codez: potresti scrivere una macro che sarebbe (questo è ciò che Play's Inception fa, anche se non fornisce questa funzionalità). La cosa bella di questo approccio è che è molto più generale: puoi usare "default" in questo modo con qualsiasi libreria JSON. –

Problemi correlati