Search code examples
scala

Scala Akka HTTP issue accessing post request parameters wrapped in Option[String]


I’m trying to create a Post request path using the Akka HTTP Server and the Scala programming language.

After I successfully make and receive the post request data, I would like to access the value of the country field inside the device parameter.

What is the best way to access this parameter?

I am using SprayJSON as the marshaller.

The current code gives the error Found: Option[Option[String]] Required: String.

Again, the request is processed successfully, I just can’t figure out how to access the value of the country field inside the device parameter.

The code is as below.

The case classes:


case class Geo(country: Option[String])

case class Device(id: String, geo: Option[Geo])

case class dataRequest(id: String, device: Option[Device])

The Post Request path:


Path(“submit”) {
   Post {
    Entity(as[dataRequest]) { data_request_params =>
 data_request_params.device.map(dev => dev.geo.map(geo => {
val geoDevice = geo.country
}))

    }
  }
}

The request body:


{
 “id”: “fghq1I123”,
 “device”: {
       “id”: “0004a672f”,
       “geo”: {
               “Country”: “AU”
       }
  }
}


Solution

  • It seems the problem you have, based on the question you asked in your previous post is not with akka neither akka-http. It's with the type system of scala.

    First you need to understand how the Option class work, and at the same time what is the difference between map and flatMap.

    • the map method has the signature Option[A].map[B](A => B): Option[B]. Which means, it will transform the value contained inside the Option (if the Option is of type Some, otherwhise it will not do anything) from A to B. For example:
    // this produces a `Option[Int]`
    val optionInt: Option[Int] = Option(1) 
    
    // this produces a `Option[String]` because the return type of the function I'm passing as parameter is a `String`
    val optionString: Option[String] = optionInt.map(int => int.toString) 
    
    // this produces a `Option[Option[Boolean]` because the return type of the function is an `Option[Int]`
    // remember that the map method expect a function A => B and returns B
    // in the following example B is of type of Option
    val optionOptionBoolean: Option[Option[Boolean]] = 
      optionInt
        .map(int => 
          optionString
            .map(string => int == string)
        )
    
    • flatMap method has the signature Option[A].flatMap[B](A => Option[B]): Option[B]. As you can see, the function expected is an Option[B] and the return type is Option[B]. This is the one that does the magic you need for this specific case
    // a class with an Option field
    case class NestedOption(optionInt: Option[Int])
    // a class with a field that is an Option of another class that contains an Option field
    case class Root(nestedOption: Option[NestedOption])
    
    val nestedOption: Option[NestedOption] = Some(NestedOption(1))
    val root: Root = Root(nestedOption)
    
    // to access the nested Option field and transform from Option[Int] to Option[String]
    val optionString = root
      .flatMap( nestedOption => // I have access to the field `nestedOption` of Root
        nestedOption
          .optionInt               // this field is of type Option, where I can call `map`
          .map( int => i.toString) // returning an Option[String] for the `flatMap`
      )
    
    // this can also be written much cleaner using for yield
    val optionString = for {
      nestedOption <- root
      int          <- nestedOption.nestedOption.optionInt
    } yield int.toString
    

    To fix the error in your code

    Path(“submit”) {
      Post {
        Entity(as[dataRequest]) { data_request_params =>
          // the code below will return an Option of the value type you return in the last line
          // if some of the `Option`s are None, you will get a `None`
          val anotherOption = for {
            device  <- data_request_params.device
            geo     <- device.geo
            country <- geo.country
          } yield // the logic you need to apply here
        }
      }
    }
    

    Here you have a good link to learn more about Option

    At the same time, I suggest to first learn the basics of Scala.

    • docs from the official scala page: it has a lot of links to useful resources that shows different feature of the language and some other materials to learn about different topics
    • scala coursera specialization: the first two courses would be enough for this basic problem and some other you would have to face in the future