Skip to content
Talk to our solutions team

Reading facts

A rule can name a fact directly, amount > 1000, and for a fact you know is present, that is the right thing to write.

For everything else there is in, and the difference is not convenience.

A bare fact reference aborts the execution when the fact is missing. Every rule still on the agenda is evaluated every cycle, and a failed evaluation ends the run, however much output other rules already wrote. One optional field turns into a lost execution.

The readers cannot do that:

when in.Eq("status", "submitted") // false when status is absent
when status == "submitted" // aborts the run when status is absent

in.Eq returns false for a fact that is not there. It has no failure mode, so it needs no guard. That is the whole design: a reader that cannot raise does not need a rule author to remember anything.

A key is a path. "invariant.doctype" reads the doctype member of the invariant object, at any depth:

rule ReadNested salience 10 {
when in.Has("customer.address.city")
then out.Set("city", in.Str("customer.address.city", ""));
out.Set("exact_total", in.EqNum("totals.amount", 1200.0));
out.Set("total", in.Double("totals.amount", 0.0));
}
facts
{
"customer": { "address": { "city": "Chennai" } },
"totals": { "amount": 1200.0 }
}
out
{ "city": "Chennai", "exact_total": true, "total": 1200 }

A dotted path is also the way to reach a nested member at all: dot access on a fact (customer.address) and a second level of brackets (customer["address"]["city"]) both abort the execution. See The rule language.

There is no separate GetPath surface, deliberately, ten functions where five will do is ten things to choose wrongly between.

SituationBehaviour
A literal dot inside a keyEscape it with a backslash: "release\\.notes" reads the key release.notes
A key whose split would give an empty segmentTreated as literal, not as a path
A trailing dot, like "Work Phone Ext."Safe with no escaping, for the reason above
rule LiteralDots salience 10 {
when true
then out.Set("ext", in.Str("Work Phone Ext.", ""));
out.Set("release", in.Str("release\\.notes", ""));
}
facts
{ "Work Phone Ext.": "42", "release.notes": "beta" }
out
{ "ext": "42", "release": "beta" }
CallReturnsNotes
in.Has(path)boolWhether anything resolves at that path
in.Eq(path, value)boolString equality: the value is read as a string first, so in.Eq("amount", "12") is true for 12. False when absent
in.EqNum(path, value)boolNumeric equality against a float: write 1200.0. False when absent
in.EqInt(path, value)boolNumeric equality against an integer: write 3. False when absent

These are the ones to reach for in a when. They are total: every input produces a true or a false, never a failure.

rule RouteSubmitted "submitted claims go to review" salience 100 {
when
in.Eq("status", "submitted") && in.EqNum("amount", 0.0) == false
then
out.Set("queue", "review");
}
facts
{ "status": "submitted", "amount": 1200.0 }
out
{ "queue": "review" }

Each takes a default, which is what it returns when nothing is at the path. No guard, no abort.

CallReturns
in.Str(path, default)String
in.Int(path, default)Integer
in.Double(path, default)Float. The default must be a float literal: 0.0
in.Bool(path, default)Boolean
in.Date(path)A date; the zero date (IsZero()) when absent or unparseable
in.Map(path)A nested object; nothing when absent
in.Rows(path)A list of records; nothing when absent
in.Value(path)The typed value, or null (IsNull())
rule ReadInvoice salience 10 {
when in.Has("invoice")
then out.Set("currency", in.Str("invoice.currency", "UNKNOWN"));
out.Set("total", in.Double("invoice.total", 0.0));
out.Set("lines", in.Rows("invoice.lines"));
}
facts
{
"invoice": {
"total": 1500.0,
"lines": [ { "sku": "A-1", "qty": 2.0 }, { "sku": "B-7", "qty": 1.0 } ]
}
}
out
{
"currency": "UNKNOWN",
"total": 1500,
"lines": [ { "sku": "A-1", "qty": 2 }, { "sku": "B-7", "qty": 1 } ]
}

Passing the default explicitly is the point, the rule states what absence means instead of leaving it to whatever the zero value happens to be. A nothing result (in.Map, in.Rows on a missing path) written with out.Set leaves the key out of the output.

A value that is present is converted, not replaced by the default. A number reads as text through in.Str, numeric text reads as a number through in.Int and in.Double, and a value that does not convert reads as the zero value:

rule Conversions salience 10 {
when true
then out.Set("missing", in.Int("retries", 3));
out.Set("numeric_text", in.Int("count", 3));
out.Set("not_a_number", in.Int("status", 3));
}
facts
{ "count": "7", "status": "submitted" }
out
{ "missing": 3, "numeric_text": 7, "not_a_number": 0 }

in.GetStr, in.GetInt, in.GetFloat, in.GetBool, in.GetMap and in.Get still work: each is the reader above with the zero value as its default, in.GetStr(k) is in.Str(k, ""). They load with a warning. Write in.Str, in.Int, in.Double, in.Bool, in.Map and in.Value.

The scratch memory calls, in.MemSet, in.MemGet, in.MemGetStr, in.MemGetRows, in.MemDelete, in.MemClear and in.GetMemAsMap, are deprecated too: stage a value with out.Set and read it back with out.*. in.NewMapWithValues is deprecated for map.Of.

You want toWrite
Compare a fact you know is presentThe bare name: amount > 1000
Compare a fact that might be absentin.Eq / in.EqNum / in.EqInt
Read a value with a meaningful fallbackin.Str(path, "UNKNOWN")
Read a nested memberA dotted path: in.Double("invoice.total", 0.0)
Ask whether something arrived at allin.Has(path)