I have a common problem but still couldn't wrap my head around what I was reading around.
In a scalatra app, I am receiving the following json:
{
_type: "hello",
timestamp: 123,
data: [
{table: "stuffJ",_id: 24},
{table: "preferences",_id: 34,word: "john"}
]}
with an unknown number of elements in field 'data'. The field table will always be there to differentiate between class types. I am trying to have it parsed to class RestAPIMessage
. This is what I have so far:
implicit val jsonFormats = new DefaultFormats { outer =>
override val typeHintFieldName = "table"
override val typeHints = ShortTypeHints(List(classOf[Preferences], classOf[StuffJ]))
}
sealed trait DataJson
case class Preferences(table: String, _id: Long, word : String) extends DataJson
case class StuffJ(table: String, _id: Long) extends DataJson
case class RestAPIMessage(_type: String, timestamp: Long, data: List[DataJson])
// if sent as Json, returns a json with two "table" fields
val message = new RestAPIMessage("hello", 123, List(new StuffJ("StuffJ", 24), new Preferences("preferences", 34, "john")))
// if received as Json, fails with a "no usable value for outer"
val djson = """{"_type":"hello","timestamp":123,"data":[{"table":"StuffJ","_id":24},{"table":"table":"preferences","_id":34,"word":"john"}]}"""
Thanks for your help!
Alright I think I got this.
In the end it seems I couldn't have what I needed "out of the box", and had to write a custom serializer. I couldn't find a simple "polymorphic" example around, so I put up a minimal foobar example below:
import org.json4s.{DefaultFormats, Formats}
import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.native.Serialization.{read, write}
import org.json4s.native.Serialization
sealed trait Bar
case class Bar1(name : String, value: Int) extends Bar
case class Bar2(name : String, stuff: Int) extends Bar
case class Foo(timestamp:Long, bar: List[Bar])
var testFoo : Foo = new Foo(123, List(Bar1("bar1",1), Bar2("bar2",2)))
object BarSerializer extends CustomSerializer[Bar](format => (
{
case x: JObject =>
val name = (x \ "name").extract[String]
name match {
case "bar1" =>
val value = (x \ "value").extract[Int]
Bar1(name,value)
case "bar2" =>
val value = (x \ "stuff").extract[Int]
Bar2(name,value)
case x => throw new MappingException("Can't convert bar with name " + x + " to Bar")
}
},
{
case x: Bar =>
//if you need only the deserializing part above, I think you could replace the below with write(x)
x match {
case Bar1(a,b) =>
("name" -> a) ~ ("value" -> b)
case Bar2(a,b) =>
("name" -> a) ~ ("stuff" -> b)
}
}
))
implicit val jsonFormats: Formats = Serialization.formats(NoTypeHints) + BarSerializer
val a = write(testFoo)
//returns : String = {"timestamp":123,"bar":[{"name":"bar1","value":1},{"name":"bar2","value":2}]}
read[Foo](a)
//returns : Foo = Foo(123,List(Bar1(bar1,1), Bar2(bar2,2)))