scala.js - 从JavaScript获取复杂对象 [英] scala.js — getting complex objects from JavaScript

查看:60
本文介绍了scala.js - 从JavaScript获取复杂对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试scala.js,我必须说它给人留下了深刻的印象!但是,我尝试将它一点一点地引入到我们的生产中,与现有的JavaScript代码并行工作。我正在努力的一件事是将复杂的结构从JS传递到Scala。例如,我有来自其他JS模块的现成JS对象:

I'm trying scala.js and I must say that it's completely impressed! However, I try to introduce it into our production little-by-little, working side-by-side with existing JavaScript code. One thing I'm struggling with is passing complex structures from JS to Scala. For example, I have ready-made JS object that I've got from the other JS module:

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中有相应的类层次结构,它看起来像这样:

has some ints, some strings (all these elements have fixed key names!), some arrays in it (which in turn has some more objects in it) and some maps (which map arbitrary string keys into more objects). Everything is optional and might be missing. Obviously, it's just a made-up example, real-life objects are much more complex, but all the basics are up here. I already have the corresponding class hierarchy in Scala which looks something like that:

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])



第一次尝试



我的第一次尝试是尝试按原样使用接收器类型:

1st attempt

My first try was to a naïve attempt to just use receiver types as is:

  @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



第二次尝试



我的第二个想法是以<$ c $接收此对象c> js.Dictionary [String] 然后做了很多沉重的类型检查&类型转换。首先,我们将定义一些辅助方法来解析JS对象中的常规字符串和整数:

2nd attempt

My second idea was receiving this object as js.Dictionary[String] and then doing lots of heavy typechecking & typecasting. First we'll define some helper methods to parse regular strings and integers from JS object:

  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 对象相同来源:

Then we'll use them to parse an Item object from the same source:

  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)
  }

它,好吧,工作,但它 真的很难看。那是很多代码,很多重复。它可能可以重构以提取数组解析并将解析映射到更健全的东西,但它仍然感觉很糟糕:(

It, well, works, but it is really ugly. That's lots of code, lots of repetitions. It can probably refactored to extract array parsing and map parsing into something saner, but it still feels bad :(

尝试 @ScalaJSDefined 注释以创建facade类的行,如文档中所述:

Tried the @ScalaJSDefined annotation to create something along the lines of "facade" class, as described in documentation:

  @ScalaJSDefined
  class JSMegaObject(
    val someInt: js.Object,
    val someStr: js.Object,
    val someArray: js.Object,
    val someMap: js.Object
  ) extends js.Object

只需将其打印出来即可:

Just printing it out kind of works:

  @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)
  }

然而,只要我我试图将方法添加到JSMegaObjectfacade,这将是c将它转换为适当的Scala对应物(即使是假的这样):

However, as soon as I'm trying to add a method to JSMegaObject "facade" that will convert it to its proper Scala counterpart (even a fake one like this):

  @ScalaJSDefined
  class JSMegaObject(
    val someInt: js.Object,
    val someStr: js.Object,
    val someArray: js.Object,
    val someMap: js.Object
  ) extends js.Object {
    def toScala: MegaObject = {
      MegaObject(None, None, None, None)
    }
  }

试图通过以下方式调用它失败:

trying to call it fails with:

An undefined behavior was detected: undefined is not an instance of my.package.MainJs$MegaObject

...哪种真的让我想起了#1的尝试。

... which kind of really reminds me of attempt #1.

显然,人们仍然可以在main方法中进行所有类型转换:

Obviously, one can still do all the typecasting in the main method:

  @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, someStr, None, None)
    Console.println(r)
  }

然而,它很快变得像丑陋一样尝试#2。

However, it quickly becomes just as ugly as attempt #2.

所以,我有点沮丧。尝试#2和#3确实有效,但它确实感觉我错过了一些东西而且它应该不那么丑陋,不舒服,并且需要编写大量的JS-to-Scala类型转换器代码才能访问传入的JS对象。有什么更好的方法呢?

So, I'm kind of frustrated. Attempts #2 and #3 do work, but it really feels that I'm missing something and it shouldn't be that ugly, uncomfortable, and require to write tons of JS-to-Scala types converter code just to access the fields of an incoming JS object. What is the better way to do it?

推荐答案

你的尝试#4已经接近,但并不完全存在。你想要的不是Scala.js定义的JS类。你想要一个真正的门面 trait 。然后你可以在其伴随对象中转移它到你的Scala类的转换。您还必须小心,始终使用 js.UndefOr 作为可选字段。

Your attempt #4 is close, but not quite there. What you want is not a Scala.js-defined JS class. You want an actual facade trait. Then you can "pimp" its conversion to your Scala class in its companion object. You must also be careful to always use js.UndefOr for optional fields.

@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)
    }
  }
}

这篇关于scala.js - 从JavaScript获取复杂对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆