1

I've been working with this example from the Elastic4s manual. It is working fine until it attempts to retrieve a document that does not have a field specified in the case class.

In this example from the manual, let's say one result only had name and was missing the location field. It would yield this error:

java.util.NoSuchElementException: key not found: location

I'm looking for a good approach to deal with search results that have varying fields.

Code sample:

case class Character(name: String, location: String)

implicit object CharacterHitAs extends HitAs[Character] {
  override def as(hit: RichSearchHit): Character = {
Character(hit.sourceAsMap("name").toString, hit.sourceAsMap("location").toString) }}

val resp = client.execute {
search in "gameofthrones" / "characters" query "kings landing"
}.await

val characters :Seq[Character] = resp.as[Character]
Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102
Jimmy Hendricks
  • 263
  • 1
  • 3
  • 13

1 Answers1

0

When developing a case class with optional parameters, use Option:

case class Character(name: String, location: Option[String])

Character("Tyrion Lannister", None)

Then all you have to do is modify your data extractor to pass a None Option if it doesn't find the data:

val tyrion = Map("location" -> "King's Landing", "name" -> "Cersei Lannister")
val cersei = Map("father" -> "Tywin Lannister?", "name" -> "Cersei Lannister")
val jaime = Map("father" -> "Tywin Lannister", "location" -> "Tower of the Hand")
val characters = List(tyrion, cersei, jaime)

case class Character(name: String, location: Option[String])

characters.map(x => Character(x.getOrElse("name", "A CHARACTER HAS NO NAME"), x.get("location")))

The result of characters.map(...) is this:

res0: List[Character] = List(
        Character(Cersei Lannister,Some(King's Landing)), 
        Character(Cersei Lannister,None), 
        Character(A CHARACTER HAS NO NAME NAME,Some(Tower of the Hand)))

From the source code for RichSearchHit, sourceAsMap should return a Map object:

def sourceAsMap: Map[String, AnyRef] = if (java.sourceAsMap == null) Map.empty else java.sourceAsMap.asScala.toMap

Given that you're using a Map shorthand, you should be able to convert your code to:

case class Character(name: String, location: Option[String])

implicit object CharacterHitAs extends HitAs[Character] {
  override def as(hit: RichSearchHit): Character = {
Character(hit.sourceAsMap.getOrElse("name", "A CHARACTER HAS NO NAME"), hit.sourceAsMap.get("location")) }}
Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102
  • I completely agree the Option class is the way to go. I've been looking into this issue more and the issue and I believe the issue is in the sourceAsMap and the toString portion of the implicit object. hit.sourceAsMap("location").toString The declaration of the sourceAsMap function sources the data from the specified field in the Elasticsearch response. However, when the result is Map.empty I'm thinking the toString method is erroring. – Jimmy Hendricks Aug 19 '16 at 13:47
  • Update: I removed the toString and changed the references in the case class to AnyRef. I still get the key not found error. So I am back to digging into the library for answers. – Jimmy Hendricks Aug 19 '16 at 13:58
  • @JimHendricks Does `sourceAsMap()` not return a Scala `Map` object? If it does - and I think it does - you can call `get` on it, which should return `None` if there is no key: `hit.sourceAsMap.get("location"))` – Nathaniel Ford Aug 19 '16 at 18:52
  • 1
    Nathan, thank you. That totally solved the problem. I now have the "none" return and can code around the issue. – Jimmy Hendricks Aug 19 '16 at 19:55
  • @JimHendricks Glad to hear it! – Nathaniel Ford Aug 19 '16 at 19:56