Consider the following from the Scala interpreter:
scala> JSON.parseFull(\"\"\"{\"name\":\"jack\",\"greeting\":\"hello world\"}\"\"\")
res6: Option[Any] = Som
An Option
is returned because parseFull
has different possible return values depending on the input, or it may fail to parse the input at all (giving None
). So, aside from an optional Map
which associates keys with values, an optional List
can be returned as well if the JSON string denoted an array.
Example:
scala> import scala.util.parsing.json.JSON._
import scala.util.parsing.json.JSON._
scala> parseFull("""{"name":"jack"}""")
res4: Option[Any] = Some(Map(name -> jack))
scala> parseFull("""[ 100, 200, 300 ]""")
res6: Option[Any] = Some(List(100.0, 200.0, 300.0))
You might need pattern matching in order to achieve what you want, like so:
scala> parseFull("""{"name":"jack","greeting":"hello world"}""") match {
| case Some(m) => Console println ("Got a map: " + m)
| case _ =>
| }
Got a map: Map(name -> jack, greeting -> hello world)
Now, if you want to generate XML output, you can use the above to iterate over the key/value pairs:
import scala.xml.XML
parseFull("""{"name":"jack","greeting":"hello world"}""") match {
case Some(m: Map[_,_]) =>
<test>
{
m map { case (k,v) =>
XML.loadString("<%s>%s</%s>".format(k,v,k))
}
}
</test>
case _ =>
}
parseFull
returns an Option
because the string may not be valid JSON (in which case it will return None
instead of Some
).
The usual way to get the value out of a Some
is to pattern match against it like this:
result match {
case Some(map) =>
doSomethingWith(map)
case None =>
handleTheError()
}
If you're certain the input will always be valid and so you don't need to handle the case of invalid input, you can use the get
method on the Option
, which will throw an exception when called on None
.
You have two separate problems.
Any
.Option
and a Map
.Let's suppose we have the data:
val x: Option[Any] = Some(Map("name" -> "jack", "greeting" -> "hi"))
and suppose that we want to return the appropriate XML if there is something to return, but not otherwise. Then we can use collect
to gather those parts that we know how to deal with:
val y = x collect {
case m: Map[_,_] => m collect {
case (key: String, value: String) => key -> value
}
}
(note how we've taken each entry in the map apart to make sure it maps a string to a string--we wouldn't know how to proceed otherwise. We get:
y: Option[scala.collection.immutable.Map[String,String]] =
Some(Map(name -> jack, greeting -> hi))
Okay, that's better! Now if you know which fields you want in your XML, you can ask for them:
val z = for (m <- y; name <- m.get("name"); greet <- m.get("greeting")) yield {
<test><name>{name}</name><greeting>{greet}</greeting></test>
}
which in this (successful) case produces
z: Option[scala.xml.Elem] =
Some(<test><name>jack</name><greeting>hi</greeting></test>)
and in an unsuccessful case would produce None
.
If you instead want to wrap whatever you happen to find in your map in the form <key>value</key>
, it's a bit more work because Scala doesn't have a good abstraction for tags:
val z = for (m <- y) yield <test>{ m.map { case (tag, text) => xml.Elem(null, tag, xml.Null, xml.TopScope, xml.Text(text)) }}</test>
which again produces
z: Option[scala.xml.Elem] =
Some(<test><name>jack</name><greeting>hi</greeting></test>)
(You can use get
to get the contents of an Option
, but it will throw an exception if the Option
is empty (i.e. None
).)
You should probably use something like this:
res6 collect { case x: Map[String, String] => renderXml(x) }
Where:
def renderXml(m: Map[String, String]) =
<test><name>{m.get("name") getOrElse ""}</name></test>
The collect method on Option[A]
takes a PartialFunction[A, B]
and is a combination of filter
(by a predicate) and map
(by a function). That is:
opt collect pf
opt filter (a => pf isDefinedAt a) map (a => pf(a))
Are both equivalent. When you have an optional value, you should use map
, flatMap
, filter
, collect
etc to transform the option in your program, avoiding extracting the option's contents either via a pattern-match or via the get
method. You should never, ever use Option.get
- it is the canonical sign that you are doing it wrong. Pattern-matching should be avoided because it represents a fork in your program and hence adds to cyclomatic complexity - the only time you might wish to do this might be for performance
Actually you have the issue that the result of the parseJSON
method is an Option[Any]
(the reason is that it is an Option
, presumably, is that the parsing may not succeed and Option
is a more graceful way of handling null
than, well, null
).
But the issue with my code above is that the case x: Map[String, String]
cannot be checked at runtime due to type erasure (i.e. scala can check that the option contains a Map
but not that the Map
's type parameters are both String
. The code will get you an unchecked warning.