2016-03-08 6 views
2

spray-json полагается на присутствие в области видимости, неявный JsonWriter[T] при вызове toJson по экземпляру T.spray-json: Сериализовать общий признак

Скажем, у меня есть черта с несколькими конкретными подтипов, каждый из которых имеет JsonWriter:

trait Base 
case class Foo(a: Int) extends Base 
case class Bar(a: Int, b: Int) extends Base 
implicit val FooFormat = jsonFormat1(Foo) 
implicit val BarFormat = jsonFormat2(Bar) 

def go(o: Base) = { 
    o.toJson 
} 

go не компилируется, потому что нет JsonWriter для базы, хотя являются писатели для всех конкретные подтипы.

Как я могу реорганизовать этот код так, чтобы общие функции Base использовали соответствующие json-форматиры?

+0

Оберните свои импликации в объект и импортируйте в область 'go'. У вас может быть проблема с самим признаком, но эти импликации не будут доступны для 'go', если вы не импортируете его. – Ayubinator

+0

Извините, если мой фрагмент был неясен - мой вопрос заключается в том, как написать функцию на базе, которая использует конкретный конкретный неявный, предполагая, что набор этих имплицитов должным образом находится в области, где это должно быть. Как бы то ни было, я знаю, как писать 'go', если его аргумент равен' (o: Foo) ', но не' (o: Base) '. – ChrisB

ответ

4

Вы можете использовать общий метод с ограничениями типа и контекста. Например:

def go[T <: Base : JsonWriter](t: T) = t.toJson 
+0

Это хорошо. это ': JsonWriter', по сути, эквивалентный' (неявный fmt: JsonWriter [T]) '? – ChrisB

+0

Точно! 'def f [T: X] (t: T): T = t' будет' f: [T] (t: T) (неявное доказательство $ 1: X [T]) T' –

1

Маршаллинг Base потребует JsonFormat. Ниже приведен один из способов подхода к заявленной проблеме.

import spray.json._ 

object BaseJsonProtocol extends DefaultJsonProtocol { 
    implicit val FooFormat = jsonFormat1(Foo) 
    implicit val BarFormat = jsonFormat2(Bar) 

    implicit object BaseJsonFormat extends RootJsonFormat[Base] { 
    def write(obj: Base) = obj match { 
     case x: Foo ⇒ x.toJson 
     case y: Bar ⇒ y.toJson 
     case unknown @ _ => serializationError(s"Marshalling issue with ${unknown}") 
    } 

    def read(value: JsValue) = { 
     value.asJsObject.getFields("a","b") match { 
     case Seq(JsNumber(a)) => Foo(a.toInt) 
     case Seq(JsNumber(a), JsNumber(b)) => Bar(a.toInt, b.toInt) 
     case unknown @ _ => deserializationError(s"Unmarshalling issue with ${unknown} ") 
    } 
    } 
} 
} 

В результате BaseJsonProtocol может быть использован для Маршалла экземпляр Base следующим образом.

import BaseJsonProtocol._ 

def go(o: Base) = o.toJson 

assert (go(Foo(10)).convertTo[Foo] == Foo(10)) 
assert (go(Foo(10).asInstanceOf[Base]).convertTo[Foo] == Foo(10)) 
assert (go(Bar(10,100)).convertTo[Bar] == Bar(10, 100)) 

Надеюсь, это поможет!

1

я понял одно решение, чтобы сделать go обобщенную функцию с типом связанным, и объявить неявное значение ... «явно»

def go[T <: Base](t: T)(implicit fmt: JsonWriter[T]) = 
    t.toJson 

Теперь функция может компилировать, потому что JsonWriter обещан как функциональный параметр, и каждый сайт вызова может выполнить конкретную реализацию JsonWriter[T] в зависимости от контекста (FooFormat или BarFormat).

+0

Без 'JsonFormat' для' Base', например: 'go (anyBase)' где 'val anyBase: Base = Foo (1)' не будет компилироваться. –

+0

Да, это хороший момент - помимо вашей реализации BaseJsonFormat, есть ли другой способ реализовать его, который не требует оператора case, который перечисляет все подтипы Bar? Концептуально кажется, что вся соответствующая логика (конкретная иерархия типов JsonWriters +) уже определена, и мне было неприятно, что где-то мне пришлось дублировать эти фрагменты кода снова - либо в BaseJsonFormat, либо в отдельных функциях go для каждого типа , – ChrisB