2014-09-22 14 views
10

Sto utilizzando json4s per lavorare con oggetti JSON nel mio codice Scala. Voglio convertire i dati JSON in una rappresentazione interna. Il seguente test di apprendimento illustra il mio problema:Estrazione di tipi polimorfici in json4s

"Polimorphic deserailization" should "be possible" in { 
    import org.json4s.jackson.Serialization.write 
    val json = 
     """ 
     |{"animals": [{ 
     | "name": "Pluto" 
     | }] 
     |} 
     """.stripMargin 
    implicit val format = Serialization.formats(ShortTypeHints(List(classOf[Dog], classOf[Bird]))) 
    val animals = parse(json) \ "animals" 
    val ser = write(Animals(Dog("pluto") :: Bird(canFly = true) :: Nil)) 
    System.out.println(ser) 
    // animals.extract[Animal] shouldBe Dog("Pluto") // Does not deserialize, because Animal cannot be constructed 
} 

Supponiamo che ci sia un oggetto JSON, che ha una lista di animali. Animal è un tipo astratto e quindi non può essere istanziato. Invece, voglio analizzare la struttura JSON per restituire gli oggetti Dog o Bird. Hanno una firma diversa:

case class Dog(name: String) extends Animal 
case class Bird(canFly: Boolean) extends Animal 

Poiché la loro firma è distinta, possono essere identificate senza avere un tag di classe nell'oggetto JSON. (Per essere precisi, la struttura JSON che ricevo non fornisce quei tag).

Ho provato a serializzare un elenco di oggetti Animal (vedere il codice). Il risultato è: Ser: {"animals":[{"jsonClass":"Dog","name":"pluto"},{"jsonClass":"Bird","canFly":true}]}

Come si può vedere, durante la serializzazione, json4s aggiunge il tag di classe jsonClass.

Come posso deserializzare un oggetto JSON che non fornisce tale tag? È possibile raggiungere questo obiettivo estendendo TypeHints?

Ho anche trovato una domanda simile: [json4s]:Extracting Array of different objects con una soluzione che utilizza in qualche modo i generici anziché la sottoclasse. Tuttavia, se ho capito bene, questa soluzione non consente semplicemente di passare l'oggetto json e avere una rappresentazione interna. Dovrei invece selezionare il modulo che non è None (mentre si controllano tutti i tipi possibili nella gerarchia dell'ereditarietà.Questo è un po 'noioso, dal momento che ho più classi Polimorfiche a diverse profondità nella struttura JSON

+0

Hai mai trovare una risposta a questo? Sto affrontando la stessa sfida qui ... – borck

+0

Purtroppo, non ho trovato una risposta.Per risolvere il problema, ho concordato con il ragazzo che ha creato il JSON serializzato per aggiungere suggerimenti tipo; ma ovviamente questa non è una soluzione se non puoi influenzare lo schema JSON. Sono ancora interessato a una risposta e ho un po 'più di conoscenza di json4s di quello che avevo al momento di scrivere la domanda, quindi cercherò di trovare una soluzione. –

+0

@borck Grazie per aver rianimato la domanda. Ho trovato che estendere 'CustomSerializer' è una soluzione abbastanza semplice (anche se il codice per estrarre grandi strutture polimorfiche potrebbe diventare un po 'gonfio). Spero che questo ti aiuti anche a risolvere il tuo problema. –

risposta

15

In definitiva, in il progetto che ha portato a questa domanda, ho concordato con il ragazzo che ha creato il JSON serializzato sull'aggiunta di suggerimenti tipo per tutti i tipi polimorfici.In retrospettiva questa soluzione è probabilmente la più pulita perché consente l'estensione futura dello schema JSON senza pericoli di introdurre ambiguità

Tuttavia, esiste una soluzione abbastanza semplice (non solo una soluzione alternativa) al problema reale

Il tipo org.json4s.Formats, che è un valore implicito nel nostro ambito, fornisce una funzione +(org.json4s.Serializer[A]). Questa funzione ci consente di aggiungere nuovi serializzatori personalizzati. Quindi per ogni supertipo polimorfico (nel nostro caso si tratta solo di Animal), possiamo definire un serializzatore personalizzato. Nel nostro esempio, dove abbiamo

trait Animal 
case class Dog(name: String) extends Animal 
case class Bird(canFly: Boolean) extends Animal 

un serializzatore personalizzato che funziona senza di tipo sentori apparirebbe come segue:

class AnimalSerializer extends CustomSerializer[Animal](format => ({ 
    case JObject(List(JField("name", JString(name)))) => Dog(name) 
    case JObject(List(JField("canFly", JBool(canFly)))) => Bird(canFly) 
}, { 
    case Dog(name) => JObject(JField("name", JString(name))) 
    case Bird(canFly) => JObject(JField("canFly", JBool(canFly))) 
})) 

Grazie alla funzione + possiamo aggiungere più serializzatori personalizzati, mantenendo il default serializzatori.

case class AnimalList(animals: List[Animal]) 

val json = 
    """ 
    |{"animals": [ 
    | {"name": "Pluto"}, 
    | {"name": "Goofy"}, 
    | {"canFly": false}, 
    | {"name": "Rover"} 
    | ] 
    |} 
    """.stripMargin 
implicit val format = Serialization.formats(NoTypeHints) + new AnimalSerializer 
println(parse(json).extract[AnimalList]) 

stampe

AnimalList(List(Dog(Pluto), Dog(Goofy), Bird(false), Dog(Rover))) 
+2

Funziona davvero, grazie per aver proseguito le tue indagini ... Mi sembra che i suggerimenti tipo funzionino meglio se hai una presa sul produttore json, anche se questo diventerà rapidamente gonfio ... – borck

Problemi correlati