Converting to JavaScript
Once webR has been loaded into a web page, objects can be converted into JavaScript from the R environment. For example, it is possible perform some computation within R and then convert the resulting R object into a JavaScript object for use.
At the moment, not all R objects can be converted to JavaScript objects. Attempting to convert an unsupported R object will throw a JavaScript exception.
Explicitly converting an RObject
to JavaScript can be done by invoking the RObject.toJs()
method, which returns a JavaScript representation of the associated R object. In most cases, JavaScript conversion has been implemented by serialising the R object to an WebRDataJs
.
Subclasses of RObject
provide additional methods to convert objects into a JavaScript representation.
Serialising R objects
Invoking RObject.toJs()
on an R object serialises the object to a JavaScript object of type WebRDataJs
. This type is designed to form a tree structure, supporting an unambiguous JavaScript representation for potentially nested R objects.
Most R objects are serialised to JavaScript objects that contain the following properties,
Property | Value |
---|---|
type |
A description of the R object’s type as a string. |
names |
An array of strings corresponding to the R object’s names attribute, or null for no names. |
values |
An array containing the value(s) associated with the R object. A JavaScript null indicates a missing value, NA . |
Some R objects, such as lists or environments, contain references to other R objects. Such objects are serialised recursively. Any R objects deeper than the maximum depth setting will not be serialised and instead included in the values
array as an RObject
. By default, R objects are serialised with infinite maximum depth.
An R NULL
object is serialised to a JavaScript object of type WebRDataJsNull
. This type does not include the names
or values
properties.
The structure of serialised R objects may be updated in future versions of webR, expanding to include more R object attributes. As such, compatibility of serialised R objects between versions of webR is not guaranteed.
Serialisation options
An options
argument of type ToJsOptions
can be provided to the RObject.toJs() method for fine-grained control over how objects are serialised.
The following options are available,
Property | Description |
---|---|
depth |
How deep should nested R objects be serialised? A value of 0 indicates infinite depth. |
Example: Serialising an R double atomic vector
const primes = await webR.evalR('c(2,3,5,7,11,13)');
await primes.toJs()
{
type: 'double'
names: null
values: [2, 3, 5, 7, 11, 13]
}
Converting to JavaScript Object
R environments, lists, and atomic vectors provide a toObject()
method that converts the R object into a JavaScript object. The result of this conversion differs from the serialisation described above in that the resulting JavaScript object properties will be directly named by the components of the R object.
.objs.globalEnv.bind('foo', [1, 10, 28, 44, 26, 52]);
webR.objs.globalEnv.bind('bar', [1, 2, 3, 401, 113, 22]);
webRawait webR.objs.globalEnv.toObject();
{ bar: Proxy(Object), foo: Proxy(Object) }
By default, toObject()
will not recurse into objects and will return R object references as JavaScript values. As an override, the depth
option may be set to recurse into the object using the .toJs()
serialisation method.
.objs.globalEnv.bind('foo', [1, 10, 28, 44, 26, 52]);
webR.objs.globalEnv.bind('bar', [1, 2, 3, 401, 113, 22]);
webRawait webR.objs.globalEnv.toObject({ depth: 0 });
{
bar: {
type: "double",
names: null,
values: [1, 2, 3, 401, 113, 22],
},
foo: {
type: "double",
names: null,
values: [1, 10, 28, 44, 26, 52]
},
}
Object conversion options
The options
argument may be used to control how R objects with empty or duplicated names are converted. In the case of duplicated R component names, first wins.
const obj = await webR.evalR(`
list(foo = c(1, 2, 3), foo = c(4, 5, 6), c("x", "y", "z"))
`);
await obj.toObject({ allowEmptyKey: true, allowDuplicateKey: true});
import type { RList } from 'webr';
const obj = await webR.evalR(`
list(foo = c(1, 2, 3), foo = c(4, 5, 6), c("x", "y", "z"))
`) as RList;
await obj.toObject({ allowEmptyKey: true, allowDuplicateKey: true});
{
"": {
type: "double",
names: null,
values: ['x', 'y', 'z'],
},
foo: {
type: "double",
names: null,
values: [1, 2, 3]
},
}
The following options are available,
Property | Description |
---|---|
depth |
How deep should nested R objects be serialised? A value of 0 indicates infinite depth. |
allowEmptyKey |
Allow an empty or null key when converting the object. |
allowDuplicateKey |
Allow duplicate keys when converting the object. |
When allowEmptyKey
or allowDuplicateKey
are false
, an error is thrown in the case of empty or duplicated R component names.
Converting to JavaScript Array
R lists and atomic vectors may be converted into a JavaScript Array
value using the method toArray()
.
const recurrence = await webR.evalR('c(1.1, 2.2, 3.3, 5.5, 8.8)');
await recurrence.toArray();
[1.1, 2.2, 3.3, 5.5, 8.8]
When converting atomic vectors to JavaScript values, missing values of NA
are represented as values of null
in the resulting JavaScript representation. This conversion process may have a performance cost for very large vectors.
Accessing raw WebAssembly memory
For atomic vectors, the toTypedArray()
method may be invoked to access a copy of the object data as it exists in WebAssembly memory.
The underlying raw memory buffer as managed by R will be returned as-is, including raw pointers for R character strings and sentinel values for missing values.
const primes = await webR.evalR('c(2,3,5,7,11,13)');
await primes.toTypedArray();
import type { RDouble } from 'webr';
const primes = await webR.evalR('c(2,3,5,7,11,13)') as RDouble;
await primes.toTypedArray();
Float64Array(6) [2, 3, 5, 7, 11, 13, buffer: ArrayBuffer(48), ... ]
Converting to primitive values
Scalar R values1 may be converted into JavaScript values using various subclass methods.
const double = await webR.evalR('20');
await double.toNumber();
await webR.objs.true.toBoolean();
import type { RDouble } from 'webr';
const double = await webR.evalR('20') as RDouble;
await double.toNumber();
await webR.objs.true.toBoolean();
20
true
Each type of atomic scalar is converted into a particular JavaScript type, and so the method names are specialised.
R object type | Method | JavaScript type |
---|---|---|
RLogical |
.toBoolean() |
Boolean |
RInteger |
.toNumber() |
Number |
RDouble |
.toNumber() |
Double |
RComplex |
.toComplex() |
{ re: ..., im: ... } |
RCharacter |
.toString() |
String |
RRaw |
.toNumber() |
Number |
Converting from an R data.frame
R data.frame
objects are list objects with an additional class attribute. As such, they may be converted into JavaScript objects using the toObject()
method. When the R list object is a data.frame
, webR will automatically convert the inner atomic columns into Array
format.
const mtcars = await webR.evalR('mtcars');
await mtcars.toObject();
import type { RList } from 'webr';
const mtcars = await webR.evalR('mtcars') as RList;
await mtcars.toObject();
{
am: [1, 1, 1, ..., 1],
carb: [4, 4, 1, ..., 2],
cyl: [6, 6, 4, ..., 4]
...,
wt: [2.62, 2.875, 2.32, ..., 2.78],
}
R data.frame
objects may also be converted into a D3-style data array format using the toD3()
method. This method is only available for R objects of class data.frame
.
const mtcars = await webR.evalR('mtcars');
await mtcars.toD3();
import type { RList } from 'webr';
const mtcars = await webR.evalR('mtcars') as RList;
await mtcars.toD3();
[
{ mpg: 21, cyl: 6, disp: 160, ... },
{ mpg: 21, cyl: 6, disp: 160, ... },
{ mpg: 22.8, cyl: 4, disp: 108, ...},
...
{ mpg: 21.4, cyl: 4, disp: 121, ...},
]
Cached R objects
WebR.objs
contains named references to long-living R objects in the form of RObject
proxies. WebR.objs
is automatically populated at initialisation time, and its properties may be safely accessed once the promise returned by WebR.init()
resolves.
WebR.objs
contains references to the following R objects,
Property | JavaScript Type | R object |
---|---|---|
null |
RNull |
NULL |
true |
RLogical |
TRUE |
false |
RLogical |
FALSE |
na |
RLogical |
Logical NA |
globalEnv |
REnvironment |
The R global environment |
baseEnv |
REnvironment |
The R base environment |
Additional R object proxy hooks
The RObject
proxies are implemented with so-called hooks, for instance a hook that forwards class method invocation to the webR worker thread. The following hooks provide additional R object functionality.
Executing R functions from JavaScript
At the moment, R functions cannot be directly converted into JavaScript functions. However, references to R functions can be executed from JavaScript in a limited way. It is possible to return an R function or closure with WebR.evalR()
and wrap it as an RFunction
proxy. The R function represented by the proxy can be called by either:
Invoking the
RFunction.exec()
method on theRFunction
object.Using
()
, i.e. the normal JavaScript function call syntax.
In either case, both JavaScript or RObject
proxies can be passed as arguments to the associated R function. In the case of JavaScript values they are converted to R objects before function execution.
const fn = await webR.evalR('function(x) { 2 * x }');
const result = await fn.exec([1,2,3]);
await result.toArray()
import type { RDouble, RFunction } from 'webr';
const fn = await webR.evalR('function(x) { 2 * x }') as RFunction;
const result = await fn.exec([1,2,3]) as RDouble;
await result.toArray()
[2, 4, 6]
The result of the R computation is automatically converted back into a JavaScript representation using RObject.toJs()
if the RFunction
proxy was executed using ()
.
const sin = await webR.evalR('sin');
await sin([1,2,3]);
import type { RFunction } from 'webr';
const sin = await webR.evalR('sin') as RFunction;
await sin([1,2,3]);
{
type: 'double'
names: null
values: [0.8414709848078965, 0.9092974268256817, 0.1411200080598672]
}
Looping over R vectors
RObject
proxies are async iterable for objects with a length
property. As such, R vectors can be looped over using the JavaScript for await...of
syntax. For example,
const list = await webR.evalR('list(2,4,6,"abc")');
for await (const i of list){
const out = await i.toJs();
console.log(out);
}
import type { RList } from 'webr';
const list = await webR.evalR('list(2,4,6,"abc")') as RList;
for await (const i of list){
const out = await i.toJs();
console.log(out);
}
{ type: 'double', names: null, values: [2] }
{ type: 'double', names: null, values: [4] }
{ type: 'double', names: null, values: [6] }
{ type: 'character', names: null, values: ["abc"] }
Footnotes
A scalar value in R is an atomic vector of length 1.↩︎