ctx is the function’s input: a tree of values addressed by dotted paths such as
quote.driver.age. It is always available — it is the default argument of every function
— and it is also what you pass on to decision tables, other functions and flows.
Reading a Value#
def name = ctx.getString('quote.driver.firstname')
def age = ctx.getInteger('quote.driver.age')
def premium = ctx.getDecimal('quote.premium')
def factor = ctx.getNumber('quote.factor')
def active = ctx.getBoolean('quote.active')
def born = ctx.getDate('quote.driver.birthDate')
def bornOn = ctx.getLocalDate('quote.driver.birthDate')
def created = ctx.getDatetime('quote.createdAt') // Date including the time part
def createdOn = ctx.getLocalDatetime('quote.createdAt') // LocalDateTime
def raw = ctx.get('quote.driver') // whatever sits at that path
On ctx the method is getLocalDatetime — lowercase t. On higson and type the
same conversion is spelled getLocalDateTime, with a capital T. Using the wrong
spelling fails at runtime with a missing-method error.
getNumber returns 0.0 and getBoolean returns false when the path holds nothing —
the same silent substitution the primitive getters make everywhere else in Higson. Use
getDecimal or getInteger when a missing value has to stay missing.
Reading a Collection#
getFirst returns the first element of a collection, or null when the path is absent or
does not hold something iterable:
def firstRisk = ctx.getFirst('quote.risks')
For the whole collection, read the path and iterate it:
def risks = ctx.get('quote.risks')
def total = risks.sum { it.getDecimal('premium') ?: BigDecimal.ZERO }
Checking Before Reading#
if (ctx.has('quote.discountCode')) {
discount = higson.getDecimal('motor.discount', ctx)
}
has does not walk sub-contexts. Unlike get, it is a flat key check, so
ctx.has('quote.driver.age') is false when quote is a sub-context that holds
driver.age. For a nested path, read it and test the result for null instead.
Writing a Value#
set stores a value and returns the context, so calls chain:
ctx.set('quote.calculatedPremium', premium)
.set('quote.calculatedAt', date.currentDatetime())
set throws when the path already exists. A second set on the same path raises a
duplicate-item error rather than overwriting. To replace a value deliberately, use the
three-argument with:
ctx.with('quote.premium', premium, true) // true = allow overwrite
with(path, value) is the two-argument form and behaves like set — it also refuses to
overwrite.
Writing to the context is how you hand a computed value down to a nested lookup:
ctx.set('quote.riskClass', riskClass)
def rate = higson.getDecimal('motor.rate.by.riskclass', ctx)
When you only need the override for one lookup and do not want it to persist, prefer the
token form instead — it leaves ctx untouched:
def rate = higson.getDecimal('motor.rate.by.riskclass', ctx, 'quote.riskClass', riskClass)
How Paths Resolve#
- Paths are case-insensitive. Keys are lowercased on both write and read, so
quote.driverAgeandquote.driverageaddress the same value. - Resolution is recursive.
ctx.get('a.b.c')tries the whole key first, then splits on the first dot and looks forb.cinside the sub-context ata, and so on. Deeply nested paths therefore cost more to resolve than flat ones — worth knowing in a function that reads the same deep path inside a loop. Read it once into a local variable.
Value Holders#
getNumber and getBoolean substitute 0.0 and false for a missing path, which makes
“absent” and “zero” indistinguishable. The holder getters are the way out — they return the
value together with its Higson type, and preserve absence:
def holder = ctx.getIntegerHolder('quote.driver.age')
if (holder.isNull()) {
return null // the path really was not supplied
}
def age = holder.getInteger() // null-preserving; intValue() would give 0
One per type:
ctx.getStringHolder(path) ctx.getNumberHolder(path)
ctx.getIntegerHolder(path) ctx.getBooleanHolder(path)
ctx.getDateHolder(path) ctx.getDatetimeHolder(path)
ctx.getLocalDateHolder(path) ctx.getLocalDatetimeHolder(path)
ctx.getLocalDatetimeHolder follows the same lowercase-t spelling as
ctx.getLocalDatetime, while the equivalent on type is toLocalDateTimeHolder with a
capital T.
The full holder surface — isNull / isNotNull / isBlank, the null-preserving object
getters and the defaulting primitive ones — is described in
Built-in Objects in a Function.
Traps#
ctx.seton an existing path throws. Usectx.with(path, value, true)to overwrite.ctx.hasis flat, so it answersfalsefor nested paths thatgetresolves fine.getNumberis0.0andgetBooleanisfalsefor a missing path, with no error. Usectx.getIntegerHolder(path).isNull()when you must tell absent from zero.getLocalDatetimeonctxversusgetLocalDateTimeelsewhere — the capitalisation differs.- Avoid
ctx.get(key, SomeClass). It casts without converting, so it throws aClassCastExceptionat the call site rather than converting the value. Use the typed getters, ortype.getX(...).