10

I've been fighting with the concept of the functional way of parsing a JSON string in Scala and ran flat into the wall with Option(something) being returned. I popped the questionand the helpful answers came streaming in.

The problem is, as someone being pretty new to Scala, what is the correct way?

Currently I'm doing this:

import util.parsing.json.JSON._

object JsonSoap {
  def main(args: Array[String]) {
    val x = parseFull("""{"name":"Joe","surname":"SOAP"}""")

    val y = x collect {
      case m: Map[_, _] => m collect {
        case (key: String, value: String) => key -> value
      }
    }

    val z = for (m <- y; name <- m.get("name"); surname <- m.get("surname"))
    yield {
      <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
        <soap:Body>
          <Person>
            <Name>{name}</Name>
            <Surname>{surname}</Surname>
          </Person>
        </soap:Body>
      </soap:Envelope>
    }

    println(z)
  }
}

I'm still stuck with Some()

Is there a nice pattern to solving my problem? Surely this has to be well explored territory. How can I improve my code?

Community
  • 1
  • 1
Jack
  • 16,506
  • 19
  • 100
  • 167

3 Answers3

35

You are not "stuck" with Some - you have the advantage that you have a Some! In Java, you would be stuck with a value whose type did not express the fact that it might not actually exist!

MyThing recklessly = apiCall.getMeAThing();
recklessly.iSureHopeImNotNull();  //OH NOES!

Compare that to this

apiCall.getMeAThing foreach (_.cannotPossiblyBeNull)

The Option datatype means that the "might not exist" bit of your query is actually baked into the return type of the query. Please stick with Option - in a few weeks you will be pulling out your hair when you go to write Java code and it's not there!

Shooting down your objections

You might say:

Aw, but I need to take the value I have and add 1 to it

I say:

apiCall.getMeAThing map (_ + 1)

You might say

Aw, but I need to pass it to a method, defaulting to the empty String if I have null

I say:

foo( apiCall.getMeAThing getOrElse "" )

You might say

Aw, but I use that value to call another API method and get something else

I say:

apiCall.getMeAThing flatMap apiCall.getMeAnotherThing

You'll definitely say

Aw but that's awfully inefficient with all those object creations

I say: "try it, it will be just fine"

Community
  • 1
  • 1
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
10

Usually when you have a result wrapped in an Option, you want to use either match or one of the monadic methods map, flatMap, or foreach. For example, in your case we could do any of these

val output = z match {
  case Some(xml) => xml
  case None => "Invalid JSON"
}
println(output)

//or
z.foreach{println} //won't print anything if z is None

//or
println(z.getOrElse("Invalid JSON"))

In most cases you should avoid something like:

if (z == None) {
  println("Invalid")
} else {
  println(Z.get)
}
tstenner
  • 10,080
  • 10
  • 57
  • 92
Dan Simon
  • 12,891
  • 3
  • 49
  • 55
  • 3
    This is poor advice; you should prefer `getOrElse`, `map`, `flatMap` etc to `match` – oxbow_lakes Feb 20 '12 at 16:45
  • 2
    I think it should be self-evident here compared to the other examples I included that pattern matching on an Option is overly verbose, but the asker seems unaware of the ways to use it so I am merely giving an overview of the various methods. – Dan Simon Feb 20 '12 at 17:03
  • Well, I don't seem to be able to remove my downvote, even though the post has been edited – oxbow_lakes Feb 20 '12 at 17:15
4

Here's the thing: you may not have gotten the data you expect, in which case it is not possible to compute z. So the answer to your question really depends on your answer to one question: what will you do when you cannot compute z?

Suppose, for instance, that you just don't want to print anything if z cannot be computed (ie, it is None). In this case, you do this:

z foreach println
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681