Values

edit

Read a value

A JsValue holds any JavaScript value. Its Kind tells which one, and the As* methods read it:

using var engine = new JsEngine();
var realm = engine.CreateRealm();

static string Describe(JsValue value) => value.Kind switch
{
    JsValueKind.Undefined => "undefined",
    JsValueKind.Null => "null",
    JsValueKind.Boolean => value.AsBoolean() ? "true" : "false",
    JsValueKind.Number => value.AsNumber().ToString(CultureInfo.InvariantCulture),
    JsValueKind.String => '"' + value.AsString() + '"',
    JsValueKind.BigInt => value.AsBigInt().ToString(CultureInfo.InvariantCulture) + "n",
    JsValueKind.Symbol => value.AsSymbol().ToString(),
    JsValueKind.Object when value.IsCallable => "function",
    JsValueKind.Object => "object",
    _ => throw new UnreachableException(),
};

var values = (JsArray)realm.Execute("[42, 'text', true, null, undefined, 2n ** 70n, Symbol('id'), {}, Math.max]").AsObject();
using (engine.Enter())
{
    var descriptions = new List<string>();
    for (var i = 0u; i < values.Length; i++)
    {
        descriptions.Add(Describe(values.GetProperty(i.ToString(CultureInfo.InvariantCulture))));
    }

    Assert.Equal(["42", "\"text\"", "true", "null", "undefined", "1180591620717411303424n", "Symbol(id)", "object", "function"], descriptions);
}
KindTestRead with
UndefinedIsUndefined
NullIsNull
BooleanIsBooleanAsBoolean()
NumberIsNumberAsNumber(): a double
StringIsStringAsString()
SymbolIsSymbolAsSymbol(): a JsSymbol
BigIntIsBigIntAsBigInt(): a BigInteger
ObjectIsObjectAsObject(), or TryGetObject(), which returns null for the other kinds
  • IsNullOrUndefined tests both values that mean "no value", and IsCallable tests whether the value is a function.
  • The As* methods do not convert: they throw an InvalidOperationException for a value of another kind, for instance AsNumber on the string "5". TryGetNumber, TryGetString and TryGetBoolean return false instead. To accept any value, convert it as shown below.
  • default(JsValue) is undefined.
  • ToString() of a JsValue is a description for debugging: it never runs code of the script.

Convert a value

To convert a value of any kind with the rules of JavaScript, as Number(x), String(x) and Boolean(x) do, use the conversions of the engine:

using var engine = new JsEngine();
var realm = engine.CreateRealm();

// AsNumber, AsString... read a value of their kind: check the kind first
var length = realm.Execute("[1, 2, 3].length");
Assert.True(length.IsNumber);
Assert.Equal(3, length.AsNumber());

// The conversions of JavaScript accept any value, and may run code of the script (valueOf, toString)
var price = realm.Execute("({ amount: 5, valueOf() { return this.amount * 2; } })");
Assert.Equal(10, engine.ToNumber(price));
Assert.Equal("[object Object]", engine.ToString(price));
Assert.True(JsEngine.ToBoolean(realm.Execute("'0'")));
Assert.Equal(12, engine.ToNumber(realm.Execute("'12'")));
  • engine.ToNumber, ToString, ToBigInt and ToPropertyKey can call the valueOf, toString or Symbol.toPrimitive methods of an object, so they can run code of the script and throw a JsException. ToString throws a TypeError for a symbol.
  • engine.ToObject wraps a primitive value in its object (new Number(1)...) and throws a TypeError for undefined and null.
  • JsEngine.ToBoolean never runs code.
  • JsEngine.SameValue compares two values as Object.is does. JsValue equality (==, Equals) is the same comparison.
  • engine.IsArray tells whether a value is an array, as Array.isArray does.

Create a value

The numbers (int and double), the strings, the booleans, the objects and the symbols convert implicitly to JsValue. The other values have their own members:

using var engine = new JsEngine();
var realm = engine.CreateRealm();
using (engine.Enter())
{
    // The numbers, the strings and the booleans convert implicitly to JsValue
    realm.GlobalObject.SetProperty("count", 3);
    realm.GlobalObject.SetProperty("ratio", 0.5);
    realm.GlobalObject.SetProperty("title", "Report");
    realm.GlobalObject.SetProperty("enabled", true);
    realm.GlobalObject.SetProperty("nothing", JsValue.Null);
    realm.GlobalObject.SetProperty("big", JsValue.FromBigInt(BigInteger.Pow(2, 70)));
}

Assert.Equal("number,number,string,boolean,object,bigint", realm.Execute("[count, ratio, title, enabled, nothing, big].map(x => typeof x).join()").AsString());

For objects and arrays, see Objects and arrays.

Symbols

A JsSymbol is a property key that cannot collide with a string. JsSymbol.Create creates a new symbol, as Symbol(description) does, and engine.GetRegisteredSymbol returns the symbol of Symbol.for(key):

using var engine = new JsEngine();
var realm = engine.CreateRealm();

// A new symbol is a unique property key
var secret = JsSymbol.Create("secret");
using (engine.Enter())
{
    realm.GlobalObject.SetProperty(secret, 42);
    realm.GlobalObject.SetProperty("secretKey", secret);
}

Assert.Equal(42, realm.Execute("globalThis[secretKey]").AsNumber());
Assert.Equal("undefined", realm.Execute("typeof globalThis[Symbol('secret')]").AsString());

// The symbols of the registry are the ones of Symbol.for, in all the realms of the engine
var shared = engine.GetRegisteredSymbol("app.id");
Assert.True(shared.IsRegistered);
Assert.Same(shared, realm.Execute("Symbol.for('app.id')").AsSymbol());
Assert.Same(shared, engine.CreateRealm().Execute("Symbol.for('app.id')").AsSymbol());
  • A symbol converts implicitly to a JsPropertyKey and to a JsValue.
  • Two symbols are the same only when they are the same instance, even with the same description.
  • The registry belongs to the engine: all its realms share it, and another engine returns another symbol for the same key. IsRegistered tells whether a symbol comes from a registry; its Description is then its key.
  • The static properties of JsSymbol are the well-known symbols (Iterator, ToPrimitive, Species...), shared by all the engines.
  • Unlike the objects, a symbol can be used by several engines.

Exchange dates

A JavaScript Date is a number of milliseconds since 1970-01-01T00:00:00Z. Create one with realm.CreateDate, and read one with TryGetDateValue:

using var engine = new JsEngine();
var realm = engine.CreateRealm();
using (engine.Enter())
{
    // A Date is a number of milliseconds since 1970-01-01T00:00:00Z
    var date = realm.CreateDate(new DateTimeOffset(2026, 10, 29, 12, 0, 0, TimeSpan.Zero).ToUnixTimeMilliseconds());
    realm.GlobalObject.SetProperty("date", date);
}

Assert.Equal("2026-10-29T12:00:00.000Z", realm.Execute("date.toISOString()").AsString());

var returned = realm.Execute("new Date(Date.UTC(2000, 0, 1))").AsObject();
Assert.True(returned.TryGetDateValue(out var timeValue));
Assert.Equal(new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero), DateTimeOffset.FromUnixTimeMilliseconds((long)timeValue));

An invalid date has the time value NaN: check it before you convert it to a DateTimeOffset. For time zones and Temporal, see Intl, dates and time zones.