Deserializing case classes with Map[String,Any] properties with lift-json
Asked Answered
N

2

6

I've been struggling with something that should be simple through lift-json for days: serializing a map to JSON.

I know, I know – "Root object can't yet be List or Map" – but I'm willing to wrap in a case class for now, and I still haven't been able to get this to work. Thanks to some stack overflow help, I've got serialization working, but I can't deserialize it from a string. I get errors like "No usable value for _" and "No information known about type."

There are other, older posts on the web that indicate type hints are the answer, but that just leads to a different error like "Do not know how to deserialize __."

For Scala 2.8.0 and Lift 2.2:

import net.liftweb.json._
import net.liftweb.json.Serialization.{read, write}

case class MapWrap(data: Map[String, Any])

object Scaffold {
    def main(args: Array[String]) {

        implicit val formats = Serialization.formats(NoTypeHints)
        //implicit val formats = Serialization.formats(ShortTypeHints(List(classOf[MapWrap])))
        //implicit val formats = Serialization.formats(FullTypeHints(List(classOf[MapWrap])))

        val ser = write(new MapWrap(Map[String,Any]("key" -> "value")))
        println("JSON: " + ser)
        println(read[MapWrap](ser))

    }
}

The line println(read[MapWrap](ser)) results in the complaint "net.liftweb.json.MappingException: No usable value for data."

How can I deserialize this case class wrapper (or achieve my ultimate goal: read(write(Map("key" -> "value"))))?

Nadia answered 19/2, 2011 at 14:26 Comment(0)
K
2

This example works if you change your Map to Map[String, String]. Then the serializer knows you expect String values. Type hints are needed if your Map values are polymorphic. Like Map[String, Animal]; Dog extends Animal; Cat extends Animal etc. Now a type hint is required for Animal type. It adds "jsonClass" field to JSON which is used to decide the concrete target type.

If you can upgrade to 2.3-M1 then you no longer need to wrap the Map but can serialize the Map directly:

http://www.scala-tools.org/repo-releases/net/liftweb/lift-json_2.8.1/2.3-M1/

Krebs answered 19/2, 2011 at 16:8 Comment(3)
Thank you. In this case, the map really is [String, Any], and "string" -> "string" is just used as an example. I tried adding short and full type hints for Any and AnyRef, but that didn't do it. Am I still missing something with type hinting?Nadia
Ok I see. The easiest way to deserialize untyped structure like that is to just parse the JSON and get the values of the JSON object. (JsonParser.parse(ser) \ "data").values or if you upgraded to 2.3 without wrapper: JsonParser.parse(ser).values . You do not even need to use type hints then.Krebs
Link is broken and returns 404. scala-tools.org/repo-releases/net/liftweb/lift-json_2.8.1/…Stopoff
C
1

Alternatively, you can use set the case class params as JObject and use the .values method:

import org.junit.{Test, Assert, Before}
import org.junit.Assert._
import net.liftweb.json._

case class Element(title:String, params:JObject)
@Test
class JsonParserTest{
 implicit val formats = Serialization.formats(NoTypeHints)

 @Test
  def testLiftMapToAny{
    val result = parse("""{"title":"foobar","params":{"one":1,"two":2, "other": "give"}}""").extract[Element]

    assertEquals("foobar", result.title)
    assertEquals(Map("one" -> 1, "two" -> 2, "other" -> "give"), result.params.values)
  }
}
Conversational answered 10/6, 2011 at 18:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.