12

Say I have a case class:

case class Name(first: String, last: String)
val n1 = Name("John", "Doe")
val n2 = Name("Mary", "Doe")

and I have a java script object:

@js.native
trait MyJSObject extends js.Object { 
  def add(name: js.Object): Unit = js.native 
}

such that name should be of the format

{"first":"John","last":"Doe"}

What is the best way to convert my case object to a js.Object?

I know this can be achieved doing a upickle.write() to convert it to a json String so I guess my question is is this the best way?

user79074
  • 4,937
  • 5
  • 29
  • 57
  • Offhand, I suspect that upickle.write is the most *convenient* way to deal with it. You could probably get a somewhat more elegant solution using Shapeless, but that would be much more effort without much obvious benefit... – Justin du Coeur Mar 07 '16 at 15:52
  • Having said that how does one convert a json string to a js.Object? – user79074 Mar 07 '16 at 16:13
  • Haven't tried it myself, but I suspect that js.eval(String) would do the trick. – Justin du Coeur Mar 07 '16 at 17:25
  • Also, I should check: do you care about these being conventional Scala case classes? If their purpose is *mainly* for Scala interop, then you should probably use @ScalaJSDefined instead -- that's kind of what it's for. – Justin du Coeur Mar 07 '16 at 17:27
  • Yes this are case classes served up by my jvm so I cannot refer to a scala annotation in that part of my project from my understanding. – user79074 Mar 07 '16 at 20:31

2 Answers2

11

If you control the case class, you can just add the @JSExportAll annotation to it:

@JSExportAll
case class Name(first: String, last: String)

This will create properties named first and last. Note that this is not the same as a simple object. However, depending on what the add method does, it is sufficient (and much more lightweight than converting to JSON and parsing again).

In case you need to cross compile the case class, you can include the scalajs-stubs library in your JVM project. It will provide dummy annotations (that are not written to .class files).

If you need a js.Object, you can use the export typechecker:

val x = Name("foo", "bar")
js.use(x).as[js.Object]

This will even work with a trait that defines the fields you need:

@js.native
trait Options extends js.Object {
  def first: String = js.native
  def last: String = js.native
}

val x = Name("foo", "bar")
js.use(x).as[Options]

The js.use(x).as call will fail, if there is a mismatch between the types (i.e. if Options defines a field that Name doesn't define).

gzm0
  • 14,752
  • 1
  • 36
  • 64
  • 1
    But how do I convert a Name instance to a js.Object? – user79074 Mar 07 '16 at 19:12
  • 2
    And this solution won't work in my case, as my case class is also used on the jvm side. So I cannot make any references to the scala.scalajs package there if I understand it correctly. – user79074 Mar 07 '16 at 19:22
  • 1
    Could you add some information about limitations of this approach? What I noticed (as of Scala.js 0.6.22) is that the field names in the serialized object get `$1` or `$smth` suffix. If the resulting object needs to mutate the fields, they have to be `var`s explicitly. Also `.as[js.Object]` won't work, because it requires a trait. – laughedelic May 20 '18 at 18:02
  • Re the `$1` or `$smth` suffix in the previous comment, note that the original names are also captured in the object as indicated in this answer. So, continuing with the `case class Name(first: String, last: String)` example, besides `first$1` and `last$1`, you should also get the `first` and `last` properties. – carueda Jan 27 '19 at 02:51
  • What is `use` in Scala.js 1.x? – steinybot Apr 14 '21 at 21:49
  • `js.use(x).as` has been removed in Scala.js 1.x – gzm0 Apr 15 '21 at 04:44
  • @gzm0 so what is the Scala.js 1.x approach for converting `case class` to `js.Object`? – ecoe Jan 29 '23 at 14:41
  • `@JSExportAll` still works the same way. But there is currently not type checking facility to check exports against a JS trait. (but you can simply cast: `x.asInstanceOf[js.Object]`). If you feel that type checked exports are valuable / necessary, maybe you can file a bug against Scala.js so we can discuss there? (https://github.com/scala-js/scala-js/issues). – gzm0 Jan 29 '23 at 19:24
-1

With uPickle:

val jsValue: Js.Value = upickle.Types.writeJs[Name](n1)
val jsObject: js.Any = upickle.json.writeJs(jsValue).asInstanceOf[js.Any]
thSoft
  • 21,755
  • 5
  • 88
  • 103