2013-05-12 14 views
9

Sto cercando di utilizzare spray-json in scala per riconoscere la scelta tra Ec2Provider e OpenstackProvider quando si converte in Json e viceversa. Mi piacerebbe poter dare delle scelte in "Provider", e se quelle scelte non si adattano a quelle disponibili allora non dovrebbero convalidare.Conversione di classi di casi polimorfiche in json e back

Il mio tentativo di questo può essere visto nel seguente codice:

import spray.json._ 
import DefaultJsonProtocol._ 

case class Credentials(username: String, password: String) 
abstract class Provider 
case class Ec2Provider(endpoint: String,credentials: Credentials) extends Provider 
case class OpenstackProvider(credentials: Credentials) extends Provider 
case class Infrastructure(name: String, provider: Provider, availableInstanceTypes: List[String]) 
case class InfrastructuresList(infrastructures: List[Infrastructure]) 

object Infrastructures extends App with DefaultJsonProtocol { 
    implicit val credFormat = jsonFormat2(Credentials) 
    implicit val ec2Provider = jsonFormat2(Ec2Provider) 
    implicit val novaProvider = jsonFormat1(OpenstackProvider) 
    implicit val infraFormat = jsonFormat3(Infrastructure) 
    implicit val infrasFormat = jsonFormat1(InfrastructuresList) 

    println(
    InfrastructuresList(
     List(
     Infrastructure("test", Ec2Provider("nova", Credentials("user","pass")), List("1", "2")) 
    ) 
    ).toJson 
) 
} 

Purtroppo, non riesce perché non riesce a trovare un formattatore per Provider classe astratta.

test.scala:19: could not find implicit value for evidence parameter of type Infrastructures.JF[Provider] 

Qualcuno ha qualche soluzione per questo?

risposta

14

Quello che vuoi fare non è disponibile fuori dalla scatola (cioè tramite qualcosa come suggerimenti tipo che consentono al deserializzatore di sapere quale classe concreta istanziare), ma è certamente possibile con un piccolo lavoro di gambe. In primo luogo, l'esempio, utilizzando una versione semplificata del codice che ha scritto sopra:

case class Credentials(user:String, password:String) 
abstract class Provider 
case class Ec2Provider(endpoint:String, creds:Credentials) extends Provider 
case class OpenstackProvider(creds:Credentials) extends Provider 
case class Infrastructure(name:String, provider:Provider) 

object MyJsonProtocol extends DefaultJsonProtocol{ 
    implicit object ProviderJsonFormat extends RootJsonFormat[Provider]{ 
    def write(p:Provider) = p match{ 
     case ec2:Ec2Provider => ec2.toJson 
     case os:OpenstackProvider => os.toJson 
    } 

    def read(value:JsValue) = value match{ 
     case obj:JsObject if (obj.fields.size == 2) => value.convertTo[Ec2Provider] 
     case obj:JsObject => value.convertTo[OpenstackProvider] 
    } 
    } 

    implicit val credFmt = jsonFormat2(Credentials) 
    implicit val ec2Fmt = jsonFormat2(Ec2Provider) 
    implicit val openStackFmt = jsonFormat1(OpenstackProvider) 
    implicit val infraFmt = jsonFormat2(Infrastructure) 
} 

object PolyTest { 
    import MyJsonProtocol._ 

    def main(args: Array[String]) { 
    val infra = List(
     Infrastructure("ec2", Ec2Provider("foo", Credentials("me", "pass"))), 
     Infrastructure("openstack", OpenstackProvider(Credentials("me2", "pass2"))) 
    ) 
    val json = infra.toJson.toString 
    val infra2 = JsonParser(json).convertTo[List[Infrastructure]] 
    println(infra == infra2) 
    } 
} 

Al fine di essere in grado di serializzare le istanze/deserializzare della classe astratta Provider, ho creato un formattazione personalizzata in cui mi sto fornendo operazioni per leggere e scrivere istanze Provider. Tutto quello che sto facendo in queste funzioni è il controllo di una condizione semplice (binario qui perché ci sono solo 2 impls di Provider) per vedere di che tipo si tratta e quindi delegare alla logica per gestire quel tipo.

Per la scrittura, ho solo bisogno di sapere quale tipo di istanza è che è facile. Leggere è un po 'più complicato però. Per la lettura, sto verificando quante proprietà ha l'oggetto e dato che i due elementi hanno diversi numeri di oggetti di scena, posso differenziare quale è in questo modo. Il controllo che sto facendo qui è molto rudimentale, ma mostra il punto che se puoi guardare JSON AST e differenziare i tipi, puoi scegliere quale deserializzare. Il tuo controllo effettivo può essere semplice o complicato come vuoi, purché sia ​​deterministico nel differenziare i tipi.

+0

Grazie mille! Questo è esattamente ciò di cui avevo bisogno! – wernerb