2013-06-10 5 views
42

Ho bisogno di ottenere una semplice soluzione di serializzazione JSON con cerimonia minima. Quindi ero abbastanza felice di trovare this forthcoming Play 2.2 library. Ciò funziona perfettamente con classi di casi in chiaro, ad es.Formato JSON senza rumore per tratti sigillati con libreria Play 2.2

import play.api.libs.json._ 

sealed trait Foo 
case class Bar(i: Int) extends Foo 
case class Baz(f: Float) extends Foo 

implicit val barFmt = Json.format[Bar] 
implicit val bazFmt = Json.format[Baz] 

Ma il seguente esito negativo:

implicit val fooFmt = Json.format[Foo] // "No unapply function found" 

Come dovrei configurare il presunto estrattore mancante per Foo?

Oppure consiglieresti un'altra libreria autonoma che gestisce la mia custodia in modo più o meno completamente automatico? Non mi interessa se questo è con i macro in fase di compilazione o riflessione in fase di esecuzione, a patto che funzioni fuori dalla scatola.

+0

C'è del codice mancante? L'unica cosa che definisce 'Foo' la linea' sealed trait Foo'? Cosa ti aspetti che accada, allora? Suppongo che Json.format' potrebbe funzionare per le classi regolari se hanno un metodo 'apply()' e 'unapply()'. – Carsten

+0

Play json, così come lift json dovrebbe essere ok. Vedete, state cercando di ottenere un formato per un tratto, ma quasi tutte le librerie che forniscono la serializzazione trasparente si basano sulle classi dei casi. Basta usare classi di casi e pattern matching e si dovrebbe andare bene. – vitalii

+1

Devo essere in grado di serializzare classi di tipi. Perciò ho bisogno di un formato per un tratto sigillato che è esteso da un numero di classi di casi. Dovrebbe essere uno scenario abbastanza comune. –

risposta

23

Ecco un'implementazione manuale dell'oggetto Foo guidata:

implicit val barFmt = Json.format[Bar] 
implicit val bazFmt = Json.format[Baz] 

object Foo { 
    def unapply(foo: Foo): Option[(String, JsValue)] = { 
    val (prod: Product, sub) = foo match { 
     case b: Bar => (b, Json.toJson(b)(barFmt)) 
     case b: Baz => (b, Json.toJson(b)(bazFmt)) 
    } 
    Some(prod.productPrefix -> sub) 
    } 

    def apply(`class`: String, data: JsValue): Foo = { 
    (`class` match { 
     case "Bar" => Json.fromJson[Bar](data)(barFmt) 
     case "Baz" => Json.fromJson[Baz](data)(bazFmt) 
    }).get 
    } 
} 
sealed trait Foo 
case class Bar(i: Int ) extends Foo 
case class Baz(f: Float) extends Foo 

implicit val fooFmt = Json.format[Foo] // ça marche! 

verifica:

val in: Foo = Bar(33) 
val js = Json.toJson(in) 
println(Json.prettyPrint(js)) 

val out = Json.fromJson[Foo](js).getOrElse(sys.error("Oh no!")) 
assert(in == out) 

alternativa la definizione del formato diretto:

implicit val fooFmt: Format[Foo] = new Format[Foo] { 
    def reads(json: JsValue): JsResult[Foo] = json match { 
    case JsObject(Seq(("class", JsString(name)), ("data", data))) => 
     name match { 
     case "Bar" => Json.fromJson[Bar](data)(barFmt) 
     case "Baz" => Json.fromJson[Baz](data)(bazFmt) 
     case _  => JsError(s"Unknown class '$name'") 
     } 

    case _ => JsError(s"Unexpected JSON value $json") 
    } 

    def writes(foo: Foo): JsValue = { 
    val (prod: Product, sub) = foo match { 
     case b: Bar => (b, Json.toJson(b)(barFmt)) 
     case b: Baz => (b, Json.toJson(b)(bazFmt)) 
    } 
    JsObject(Seq("class" -> JsString(prod.productPrefix), "data" -> sub)) 
    } 
} 

Ora, idealmente, vorrei generare automaticamente i metodi apply e unapply. Sembra che avrò bisogno di usare la riflessione o di immergermi nei macro.

+0

a mio avviso l'approccio apply/unpply è piuttosto pericoloso. Se il nome della classe JSON non è esaurito (JSON malformato), la chiamata get esploderà e non avrai nessuna registrazione json error di questo. – Felix

21

MODIFICATO 2015-09-22

La biblioteca play-json-extra comprende la strategia play-json-variants, ma anche il [play-JSON-estensioni] strategia (stringa di appartamento in caso di oggetti misti con gli oggetti per casi di classi senza $ in più variante o tipo $ se necessario). Fornisce inoltre serializzatori e deserializzatori per le enumerazioni basate su macramé.

risposta precedente V'è ora una libreria chiamata play-json-variants, che permette di scrivere:

implicit val format: Format[Foo] = Variants.format[Foo] 

Questo genererà automaticamente i formati corrispondenti, sarà anche gestire disambiguazione del seguente caso con l'aggiunta di un $ attributo variante (l'equivalente di 0__ s' class attributo)

sealed trait Foo 
case class Bar(x: Int) extends Foo 
case class Baz(s: String) extends Foo 
case class Bah(s: String) extends Foo 

genererebbe

val bahJson = Json.obj("s" -> "hello", "$variant" -> "Bah") // This is a `Bah` 
val bazJson = Json.obj("s" -> "bye", "$variant" -> "Baz") // This is a `Baz` 
val barJson = Json.obj("x" -> "42", "$variant" -> "Bar") // And this is a `Bar` 
+0

Grazie per la nuova risposta. Non capisco perché l'autore abbia sostanzialmente riscritto cosa [ho fatto] (https://github.com/Sciss/play-json-sealed), ma bene ... Stiamo avendo lo stesso problema con 'knownDirectSubclasses' non fornito in modo sicuro dal sistema macro (e conferma che questo non sarà risolto presto) –

+0

Molto probabilmente non lo sapeva ... proprio come me :) – Jean

+0

non sapresti di una libreria che crea formati con valori predefiniti per le proprietà mancanti (vedi http://stackoverflow.com/questions/20616677/defaults-for-missing-properties-in-play-2-json-formats per i dettagli) – Jean

3

Una piccola correzione per la risposta precedente 0__ per quanto riguarda la definizione del formato diretta - la legge metodo non ha funzionato, e qui è la mia refactoring ad esso, per diventare anche più idiomatica -

def reads(json: JsValue): JsResult[Foo] = { 

    def from(name: String, data: JsObject): JsResult[Foo] = name match { 
    case "Bar" => Json.fromJson[Bar](data)(barFmt) 
    case "Baz" => Json.fromJson[Baz](data)(bazFmt) 
    case _ => JsError(s"Unknown class '$name'") 
    } 

    for { 
    name <- (json \ "class").validate[String] 
    data <- (json \ "data").validate[JsObject] 
    result <- from(name, data) 
    } yield result 
} 
Problemi correlati