scala.js – 从JavaScript获取复杂对象
我正在尝试
scala.js,我必须说它给人留下了深刻的印象!但是,我尝试将它一点一点地引入到我们的生产中,与现有的
JavaScript代码并行工作.我正在努力的一件事是将复杂的结构从JS传递到Scala.例如,我有来自其他JS模块的现成JS对象:
h = { "someInt": 123,"someStr": "hello","someArray": [ {"name": "a book","price": 123},{"name": "a newspaper","price": 456} ],"someMap": { "Knuth": { "name": "The Art of Computer Programming","price": 789 },"Gang of Four": { "name": "Design Patterns: Blah-blah","price": 1234 } } } It 有一些整数,一些字符串(所有这些元素都有固定的键名!),其中有一些数组(其中还有一些对象)和一些映射(将任意字符串键映射到更多对象).一切都是可选的,可能会丢失.显然,它只是一个虚构的例子,现实生活中的对象要复杂得多,但所有的基础都在这里.我已经在Scala中有相应的类层次结构,它看起来像这样: case class MegaObject( someInt: Option[Int],someStr: Option[String],someArray: Option[Seq[Item]],someMap: Option[Map[String,Item]] ) case class Item(name: Option[String],price: Option[Int]) 第一次尝试 我的第一次尝试是尝试按原样使用接收器类型: @JSExport def try1(src: MegaObject): Unit = { Console.println(src) Console.println(src.someInt) Console.println(src.someStr) } 它显然失败了: An undefined behavior was detected: [object Object] is not an instance of my.package.MainJs$MegaObject 第二次尝试 我的第二个想法是将这个对象作为js.Dictionary [String]接收,然后做了很多重的类型检查&类型转换.首先,我们将定义一些辅助方法来解析JS对象中的常规字符串和整数: def getOptStr(obj: js.Dictionary[String],key: String): Option[String] = { if (obj.contains(key)) { Some(obj(key)) } else { None } } def getOptInt(obj: js.Dictionary[String],key: String): Option[Int] = { if (obj.contains(key)) { Some(obj(key).asInstanceOf[Int]) } else { None } } 然后我们将使用它们来解析来自同一源的Item对象: def parseItem(src: js.Dictionary[String]): Item = { val name = getOptStr(src,"name") val price = getOptInt(src,"price") Item(name,price) } 然后,一起解析整个MegaObject: @JSExport def try2(src: js.Dictionary[String]): Unit = { Console.println(src) val someInt = getOptInt(src,"someInt") val someStr = getOptStr(src,"someStr") val someArray: Option[Seq[Item]] = if (src.contains("someArray")) { Some(src("someArray").asInstanceOf[js.Array[js.Dictionary[String]]].map { item => parseItem(item) }) } else { None } val someMap: Option[Map[String,Item]] = if (src.contains("someMap")) { val m = src("someMap").asInstanceOf[js.Dictionary[String]] val r = m.keys.map { mapKey => val mapVal = m(mapKey).asInstanceOf[js.Dictionary[String]] val item = parseItem(mapVal) mapKey -> item }.toMap Some(r) } else { None } val result = MegaObject(someInt,someStr,someArray,someMap) Console.println(result) } 它很好用,但真的很难看.那是很多代码,很多重复.它可能可以重构以提取数组解析并将解析映射到更健全的东西,但它仍然感觉不好:( 第三次尝试 尝试使用@ScalaJSDefined注释来创建“facade”类的行,如文档中所述: @ScalaJSDefined class JSMegaObject( val someInt: js.Object,val someStr: js.Object,val someArray: js.Object,val someMap: js.Object ) extends js.Object 只需打印出一些作品: @JSExport def try3(src: JSMegaObject): Unit = { Console.println(src) Console.println(src.someInt) Console.println(src.someStr) Console.println(src.someArray) Console.println(src.someMap) } 但是,只要我尝试向JSMegaObject“facade”添加一个方法,它就会将它转换为适当的Scala对应物(即使是像这样的伪造的): @ScalaJSDefined class JSMegaObject( val someInt: js.Object,val someMap: js.Object ) extends js.Object { def toScala: MegaObject = { MegaObject(None,None,None) } } 尝试调用它失败了: An undefined behavior was detected: undefined is not an instance of my.package.MainJs$MegaObject ……哪种方式让我想起了#1的尝试. 显然,仍然可以在main方法中完成所有类型转换: @JSExport def try3real(src: JSMegaObject): Unit = { val someInt = if (src.someInt == js.undefined) { None } else { Some(src.someInt.asInstanceOf[Int]) } val someStr = if (src.someStr == js.undefined) { None } else { Some(src.someStr.asInstanceOf[String]) } // Think of some way to access maps and arrays here val r = MegaObject(someInt,None) Console.println(r) } 然而,它很快变得像尝试#2一样难看. 结论到目前为止 所以,我有点沮丧.尝试#2和#3确实有效,但它确实感觉我错过了一些东西而且它应该不那么丑陋,不舒服,并且需要编写大量的JS-to-Scala类型转换器代码才能访问传入的JS对象.有什么更好的方法呢? 解决方法
你的尝试#4很接近,但并不完全存在.你想要的不是Scala.js定义的JS类.你想要一个真正的门面特征.然后你可以在其伴随对象中“转移”它到你的Scala类的转换.您还必须小心,始终将js.UndefOr用于可选字段.
@ScalaJSDefined trait JSMegaObject extends js.Object { val someInt: js.UndefOr[Int] val someStr: js.UndefOr[String],val someArray: js.UndefOr[js.Array[JSItem]],val someMap: js.UndefOr[js.Dictionary[JSItem]] } object JSMegaObject { implicit class JSMegaObjectOps(val self: JSMegaObject) extends AnyVal { def toMegaObject: MegaObject = { MegaObject( self.someInt.toOption,self.someStr.toOption,self.someArray.toOption.map(_.map(_.toItem)),self.someMap.toOption.map(_.mapValues(_.toItem))) } } } @ScalaJSDefined trait JSItem extends js.Object { val name: js.UndefOr[String] val price: js.UndefOr[Int] } object JSItem { implicit class JSItemOps(val self: JSItem) extends AnyVal { def toItem: Item = { Item( self.name.toOption,self.price.toOption) } } } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |