5

I'm running into a situation where I'm retrieving some Json from an external server (I do not have any control over that server). The Json has one element that may occur 1 or more times. I'm trying to parse it using the net.liftweb.json facilities and that works fine only if the element occurs more than once. If the element occurs only once, it fails to parse.

Here's some example code:

import net.liftweb.json._
import net.liftweb.json.JsonDSL._

case class JSonListIssue(foo: List[String])

class JSonTest extends TestCase {

  implicit val formats = net.liftweb.json.DefaultFormats; 

  def testJsonList {
    val jsonStr2Foos = "{\"foo\": \"bar\", \"foo\": \"bar2\"}"
    val json = (parse(jsonStr2Foos).extract[JSonListIssue]) 
    assertEquals(2, json.foo.size)

    val jsonStr1Foo = "{\"foo\": \"bar\"}"
    val json2 = (parse(jsonStr1Foo).extract[JSonListIssue]) // Results in Json MappingException
    assertEquals(1, json2.foo.size)
  }

}

The second parse statement fails in the above code. If I would define the case class as follows, the second parse would work, but the first would fail.

    case class JSonListIssue(foo: String)

Any suggestions on how to solve this in a clean way? I could of course catch the MappingException and then parse it using the other case class, but that dirty...

Thanks, Gero

Gero
  • 2,967
  • 6
  • 22
  • 20
  • FYI you can use a triple-quoted string in scala. That way, `jsonStr2Foos = """{"foo": "bar", "foo": "bar2"}"""` and you don't have to escape all of those quotes. – Dylan Sep 28 '12 at 14:40
  • What if you try instead: case class FooBar (foo:String, bar:String), and then: val json = parse(jsonStrFoo).extract(List[FooBar]) ? – onnoweb Sep 28 '12 at 15:18
  • Never mind, that doesn't work. I read your input wrongly. – onnoweb Sep 28 '12 at 16:05

1 Answers1

0

So first of all, while you have no control over whoever wrote that API, do kindly shoot them in the foot for doing something this silly if you ever meet them. :P

So, it's not the cleanest solution in the world, but I think I came up with something that'll work for you. It's possible to directly query the JValue produced by parsing the JSON using the \ operator.

So, something like this should work for you.

case class JsonListIssue(foo: List[String])

def extractJsonListIssue(json: JValue) = {
  json \ "foo" match {
    case JString(foo) =>
      JsonListIssue(List(foo))

    case _ =>
      json.extract[JsonListIssue]
  }
}

You may or may not need parens around the json \ "foo" for that to compile. But! I think that will work for you for most cases. FWIW, if you want to be really Lift-y you should seriously consider using a Box here and use tryo to turn any exceptions from extract into a Failure that you can catch higher up in the call stack. So, that would look something like this:

// Add these guys to your existing imports
import net.liftweb._
  import common._
  import util.Helpers._

case class JsonListIssue(foo: List[String])

def extractJsonListIssue(json: JValue) = {
  json \ "foo" match {
    case JString(foo) =>
      Full(JsonListIssue(List(foo)))

    case _ =>
      // Will return a Full with the result of the method on
      // success and a Failure if extract throws an exception.
      tryo(json.extract[JsonListIssue])
  }
}

You could then use this in a for comprehension elsewhere in your code.

Let me know if this doesn't work. I'll let you know if anything cleaner occurs to me. Cheers, mate!

Matt Farmer
  • 709
  • 4
  • 13