2016-04-25 11 views
7

posso facilmente genericamente derivare un codec per una famiglia di classe cassa sigillata simili:Derivando circe Codec per una famiglia caso classe chiusa dove basamento tratto ha un (sigillato) Tipo di utente

import io.circe._ 
import io.circe.generic.auto._ 

sealed trait Base 
case class X(x: Int) extends Base 
case class Y(y: Int) extends Base 

object Test extends App { 
    val encoded = Encoder[Base].apply(Y(1)) 
    val decoded = Decoder[Base].apply(encoded.hcursor) 
    println(decoded) // Right(Y(1)) 
} 

Tuttavia, se aggiungo un membro di tipo alla classe di base che non posso più farlo, anche se è delimitata da un tratto sigillato:

import io.circe._ 
import io.circe.generic.auto._ 

sealed trait Inner 
case class I1(i: Int) extends Inner 
case class I2(s: String) extends Inner 

sealed trait Base { type T <: Inner } 
case class X[S <: Inner](x: S) extends Base { final type T = S } 
case class Y[S <: Inner](y: S) extends Base { final type T = S } 

object Test extends App { 
    val encodedInner = Encoder[Inner].apply(I1(1)) 
    val decodedInner = Decoder[Inner].apply(encodedInner.hcursor) // Ok 
    println(decodedInner) // Right(I1(1)) 

    // Doesn't work: could not find implicit for Encoder[Base] etc 
    // val encoded = Encoder[Base].apply(Y(I1(1))) 
    // val decoded = Decoder[Base].apply(encoded.hcursor) 
    // println(decoded) 
} 

c'è un modo per ottenere ciò che voglio? In caso contrario, cosa posso cambiare per ottenere qualcosa di simile?

+0

E se provaste con il pattern aux? per esempio. 'type Aux [A <: Input] = Base {type T = A}' quindi estendere da 'Aux'? Inoltre, hai davvero bisogno che sia un membro del tipo? – pyrospade

+0

In effetti, sembra che le vostre classi case possano semplicemente prendere un 'Inner' come argomento invece di un' S <: Inner'. – pyrospade

+0

Ho aggiunto una risposta, ma da allora la ho rivista per aggiungere ulteriori dettagli e spiegazioni, oltre a una migliore implementazione. – pyrospade

risposta

1

Il motivo principale per questo non funziona è perché si sta cercando di fare essenzialmente

Encoder[Base { type T }] 

da sé che tipo T è. È analogo aspettarsi che questa funzione si compili -

def foo[A] = implicitly[Encoder[List[A]]] 

È necessario perfezionare esplicitamente il tipo.

Un modo per avvicinarsi a questo è con il modello Aux. Non è possibile utilizzare il tipico type Aux[S] = Base { type T = S } dal momento che non fornirà il coprodotto quando si tenta di derivare l'istanza (le classi X e Y non possono estendersi da un alias di tipo). Invece, potremmo fare un giro intorno creando un altro tratto sigillato come Aux e le nostre classi di casi si estendono da quello.

Fino a quando tutte le classi case si estendono dalla Base.Aux invece che direttamente da Base, è possibile utilizzare il seguente, che gli abusi .asInstanceOf per placare il sistema di tipi.

sealed trait Inner 
case class I1(i: Int) extends Inner 
case class I2(s: String) extends Inner 

sealed trait Base { type T <: Inner } 
object Base { 
    sealed trait Aux[S <: Inner] extends Base { type T = S } 
    implicit val encoder: Encoder[Base] = { 
    semiauto.deriveEncoder[Base.Aux[Inner]].asInstanceOf[Encoder[Base]] 
    } 
    implicit val decoder: Decoder[Base] = { 
    semiauto.deriveDecoder[Base.Aux[Inner]].asInstanceOf[Decoder[Base]] 
    } 
} 

val encoded = Encoder[Base].apply(Y(I1(1))) 
val decoded = Decoder[Base].apply(encoded.hcursor) 

Si noti che molto dipende da come si stanno effettivamente utilizzando i tipi. Immagino che non ti affidi chiamando lo Encoder[Base] direttamente e preferisci usare lo import io.circe.syntax._ e chiamare il metodo di estensione .asJson. In tal caso, potresti essere in grado di fare affidamento su un'istanza Encoder[Base.Aux[S]] che verrebbe dedotta in base al valore codificato/decodificato. Qualcosa come il seguente potrebbe essere sufficiente per il tuo caso d'uso senza ricorrere agli hack .asInstanceOf.

implicit def encoder[S <: Inner : Encoder]: Encoder[Base.Aux[S]] = { 
    semiauto.deriveEncoder 
} 

Di nuovo, tutto dipende da come si utilizzano le istanze. Sono scettico sul fatto che tu abbia effettivamente bisogno di un membro di tipo in Base, le cose sarebbero più semplici se lo spostassi su un parametro generico in modo che il deriver potesse capire il coprodotto per te.

+0

Il membro del tipo è lì per un motivo che è fuori ambito, può certamente essere rimosso ma sovraccaricherà la sintassi con molti parametri di tipo cascading ovunque. Sicuramente può essere fatto in modo diverso, ma non è questo il punto che credo. –

+0

Inoltre, come estenderesti da Base.Aux dove Aux è un membro di tipo del compagno? Aux è solo un alias di tipo, non è possibile estenderlo afaik, non vedo la differenza comunque –

+0

Ma nel codice precedente, Base.Aux è un tratto, non un alias di tipo ... – Blaisorblade

Problemi correlati