Jak Mogę przekształcić mapę do klasy case w Scali?

Jeśli mam Map[String,String]("url" -> "xxx", "title" -> "yyy"), Czy istnieje sposób, aby generycznie przekształcić go w case class Image(url:String, title:String)?

Mogę napisać pomocnika:

object Image{
  def fromMap(params:Map[String,String]) = Image(url=params("url"), title=params("title"))
}

Ale czy jest jakiś sposób na ogólne napisanie tego raz na mapę do jakiejś klasy przypadków?

Author: tommy chheng, 2011-05-31

5 answers

Po pierwsze, istnieje kilka bezpiecznych alternatyw, które możesz zrobić, jeśli chcesz tylko skrócić kod. Obiekt towarzyszący może być traktowany jako funkcja, więc można użyć czegoś takiego:

def build2[A,B,C](m: Map[A,B], f: (B,B) => C)(k1: A, k2: A): Option[C] = for {
  v1 <- m.get(k1)
  v2 <- m.get(k2)
} yield f(v1, v2)

build2(m, Image)("url", "title")

Zwróci opcję zawierającą wynik. Alternatywnie możesz użyć ApplicativeBuilder s W Scalaz, które wewnętrznie robią prawie to samo, ale z ładniejszą składnią:

import scalaz._, Scalaz._
(m.get("url") |@| m.get("title"))(Image)

Jeśli naprawdę musisz to zrobić poprzez odbicie, najprostszym sposobem byłoby użycie Paranamera (jako Lift-Framework does). Paranamer może przywrócić nazwy parametrów sprawdzając kod bajtowy, więc występuje spadek wydajności i nie będzie działać we wszystkich środowiskach z powodu problemów z classloaderem (na przykład REPL). Jeśli ograniczysz się do klas posiadających tylkoString parametry konstruktora, możesz to zrobić tak:

val pn = new CachingParanamer(new BytecodeReadingParanamer)

def fill[T](m: Map[String,String])(implicit mf: ClassManifest[T]) = for {
  ctor <- mf.erasure.getDeclaredConstructors.filter(m => m.getParameterTypes.forall(classOf[String]==)).headOption
  parameters = pn.lookupParameterNames(ctor)
} yield ctor.newInstance(parameters.map(m): _*).asInstanceOf[T]

val img = fill[Image](m)

(zauważ, że ten przykład może wybrać konstruktor domyślny, ponieważ nie sprawdza liczby parametrów, które chcesz zrobić)

 8
Author: Moritz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-31 19:19:40

Oto rozwiązanie wykorzystujące wbudowaną Scala / java reflection:

  def createCaseClass[T](vals : Map[String, Object])(implicit cmf : ClassManifest[T]) = {
      val ctor = cmf.erasure.getConstructors().head
      val args = cmf.erasure.getDeclaredFields().map( f => vals(f.getName) )
      ctor.newInstance(args : _*).asInstanceOf[T]
  }

Aby go użyć:

val image = createCaseClass[Image](Map("url" -> "xxx", "title" -> "yyy"))
 6
Author: Andrejs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-04 19:57:42

Nie pełna odpowiedź na twoje pytanie, ale początek ...

Można to zrobić, ale prawdopodobnie będzie to bardziej skomplikowane, niż myślałeś. Każda wygenerowana Klasa Scala jest opatrzona adnotacją w języku Java ScalaSignature, którego element bytes może być parsowany, aby podać potrzebne metadane (w tym nazwy argumentów). Format tego podpisu nie jest jednak API, więc musisz go przeanalizować samodzielnie (i prawdopodobnie zmienisz sposób przetwarzania każdej nowej głównej Scali release).

Być może najlepszym miejscem na początek jest biblioteka lift-json , która ma możliwość tworzenia instancji klas przypadków na podstawie danych JSON.

Update: myślę, że lift-json używa do tego celu Paranamer , a więc może nie parsować bajtów ScalaSignature... co sprawia, że ta technika działa również dla klas spoza Scali.

Update 2: Zobacz odpowiedź Moritza zamiast tego, kto jest lepiej poinformowany niż ja.

 2
Author: Jean-Philippe Pellet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:13

Jest hack, który można przekształcić map do json, a następnie do klasy case. Użyłem spray-json

import spray.json._

object MainClass2 extends App {
  val mapData: Map[Any, Any] =
    Map(
      "one" -> "1",
      "two" -> 2,
      "three" -> 12323232123887L,
      "four" -> 4.4,
      "five" -> false
    )

  implicit object AnyJsonFormat extends JsonFormat[Any] {
    def write(x: Any): JsValue = x match {
      case int: Int           => JsNumber(int)
      case long: Long          => JsNumber(long)
      case double: Double        => JsNumber(double)
      case string: String        => JsString(string)
      case boolean: Boolean if boolean  => JsTrue
      case boolean: Boolean if !boolean => JsFalse
    }
    def read(value: JsValue): Any = value match {
      case JsNumber(int) => int.intValue()
      case JsNumber(long) => long.longValue()
      case JsNumber(double) => double.doubleValue()
      case JsString(string) => string
      case JsTrue      => true
      case JsFalse     => false
    }
  }

  import ObjJsonProtocol._
  val json = mapData.toJson
  val result: TestObj = json.convertTo[TestObj]
  println(result)

}

final case class TestObj(one: String, two: Int, three: Long, four: Double, five: Boolean)

object ObjJsonProtocol extends DefaultJsonProtocol {
  implicit val objFormat: RootJsonFormat[TestObj] = jsonFormat5(TestObj)
}

I użyj tej zależności w SBT build:

 "io.spray"          %%   "spray-json"     %   "1.3.3"
 0
Author: Tanvir Hassan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-25 11:04:52

Nie można tego zrobić, ponieważ trzeba by pobrać nazwy parametrów metody apply obiektu towarzyszącego i po prostu nie są one dostępne przez reflection. Jeśli masz wiele z tych klas przypadków, możesz przeanalizować ich deklaracje i wygenerować metody frommap.

 -2
Author: Kim Stebel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-31 04:21:58