Search code examples
scalaelasticsearchelastic4s

Map one Scala case class with a certain structure to another with a different structure


I am building a Scala API that talks to Elasticsearch using Elastic4s.

One response I receive from Elastic4s is of the type SearchHit and has the structure:

SearchHit(id: String,
          index: String,
          `type`: String,
          score: Float,
          private val _source: Map(name: String,
                                   code: String,
                                   location: Map(lat: Double, lon:Double)
                                  )
          )

I need to map this object to another object of the structure:

case class Location(id: Option[String] = None, location: GeoLocation, code: String, name: String)

where GeoLocation is:

case class GeoLocation(lat: Double, lon: Double)

As you can see, most of the fields I need to be mapped to Location are inside the _source Map but I also need the id to be mapped.


Solution

  • Elastic4s provides a typeclass that will help. It's called HitReader (although it might change name in version 6 - to be released), and if you implement this for your type Location then you can call .to[T] on the search results.

    object LocationHitReader extends HitReader {
      def read(hit: Hit): Either[Throwable, T] = ...
    }
    

    Implementing this by hand doesn't get you much further than where you are at the moment, but elastic4s then provides implementations using the common json backends - Jackson, Json4s, Circe, Spray-Json so you don't have to do anything.

    If you use Jackson for example, then you add elastic4s-jackson to your classpath and then add the following import above your search results.

    import ElasticJackson.Implicits._

    Then, on your search result, you can call .to[Location] to get a Seq[Location] or .safeTo[Location] to get an Seq[Either[T, Location]

    To get the id out as well, you could create a custom HitReader which delegates to the library created one, adding the id.