Skip to content

Latest commit

 

History

History
236 lines (179 loc) · 8.13 KB

README.md

File metadata and controls

236 lines (179 loc) · 8.13 KB

Android Components > Libraries > JEXL

Javascript Expression Language: Powerful context-based expression parser and evaluator.

This implementation is based on Mozjexl, a fork of Jexl (designed and created at TechnologyAdvice) for use at Mozilla, specifically as a part of SHIELD and Normandy.

Features not supported yet:

  • JavaScript object properties (e.g. String.length)
  • Adding custom operators (binary/unary)

Other implementations:

Usage

Setting up the dependency

Use Gradle to download the library from maven.mozilla.org (Setup repository):

implementation "org.mozilla.components:jexl:{latest-version}

Evaluating expressions

val jexl = Jexl()

val result = jexl.evaluate("75 > 42")

// evaluate() returns an object of type JexlValue. Calling toKotlin() converts this
// into a matching Kotlin type (in this case a Boolean).
println(result.value) // Prints "true"

Often expressions should return a Booleanvalue. In this case evaluateBooleanExpression is a helper that always returns a Kotlin Boolean and never throws an exception (Returns false).

val jexl = Jexl()

// "result" has type Boolean and value "true"
val result = jexl.evaluateBooleanExpression("42 + 23 > 50", defaultValue = false)

Unary Operators

Operation Symbol
Negate !

Binary Operators

Operation Symbol
Add, Concat +
Subtract -
Multiply *
Divide /
Divide and floor //
Modulus %
Power of ^
Logical AND &&
Logical OR ||

Comparison

Comparison Symbol
Equal ==
Not equal !=
Greater than >
Greater than or equal >=
Less than <
Less than or equal <=
Element in array or string in

Ternary operator

Conditional expressions check to see if the first segment evaluates to a truthy value. If so, the consequent segment is evaluated. Otherwise, the alternate is. If the consequent section is missing, the test result itself will be used instead.

Expression Result
"" ? "Full" : "Empty" Empty
"foo" in "foobar" ? "Yes" : "No" Yes
{agent: "Archer"}.agent ?: "Kane" Archer

Native Types

Type Examples
Booleans true, false
Strings "Hello "user"", 'Hey there!'
Integers 6, -7, 5, -3
Doubles -7.2, -3.14159
Objects {hello: "world!"}
Arrays ['hello', 'world!']
Undefined undefined

The JavaScript implementation of Jexl uses a Numeric type. This implementation dynamically casts between Integer and Double as needed.

Groups

Parentheses work just how you'd expect them to:

Expression Result
(83 + 1) / 2 42
1 < 3 && (4 > 2 &#124;&#124; 2 > 4) true

Identifiers

Access variables in the context object by just typing their name. Objects can be traversed with dot notation, or by using brackets to traverse to a dynamic property name.

Example context:

{
    name: {
        first: "Malory",
        last: "Archer"
    },
    exes: [
        "Nikolai Jakov",
        "Len Trexler",
        "Burt Reynolds"
    ],
    lastEx: 2
}
Expression Result
name.first Malory
name['la' + 'st'] Archer
exes[2] Burt Reynolds
exes[lastEx - 1] Len Trexler

Collections

Collections, or arrays of objects, can be filtered by including a filter expression in brackets. Properties of each collection can be referenced by prefixing them with a leading dot. The result will be an array of the objects for which the filter expression resulted in a truthy value.

Example context:

{
    employees: [
        {first: 'Sterling', last: 'Archer', age: 36},
        {first: 'Malory', last: 'Archer', age: 75},
        {first: 'Lana', last: 'Kane', age: 33},
        {first: 'Cyril', last: 'Figgis', age: 45},
        {first: 'Cheryl', last: 'Tunt', age: 28}
    ],
    retireAge: 62
}
Expression Result
employees[.first == 'Sterling'] [{first: 'Sterling', last: 'Archer', age: 36}]
employees[.last == 'Tu' + 'nt'].first Cheryl
employees[.age >= 30 && .age < 40] [{first: 'Sterling', last: 'Archer', age: 36},{first: 'Lana', last: 'Kane', age: 33}]
employees[.age >= 30 && .age < 40][.age < 35] [{first: 'Lana', last: 'Kane', age: 33}]
employees[.age >= retireAge].first Malory

Transforms

The power of Jexl is in transforming data. Transform functions take one or more arguments: The value to be transformed, followed by anything else passed to it in the expression.

val jexl = Jexl()

jexl.addTransform("split") { value, arguments ->
    value.toString().split(arguments.first().toString()).toJexlArray()
}

jexl.addTransform("lower") { value, _ ->
    value.toString().toLowerCase().toJexl()
}

jexl.addTransform("last") { value, _ ->
    (value as JexlArray).values.last()
}
Expression Result
`"Pam Poovey"|lower|split(' ') first`
"password==guest"&#124;split('=' + '=') ['password', 'guest']

Context

Variable contexts are straightforward Objects that can be accessed in the expression.

val context = Context(
    "employees" to JexlArray(
        JexlObject(
            "first" to "Sterling".toJexl(),
            "last" to "Archer".toJexl(),
            "age" to 36.toJexl()),
        JexlObject(
            "first" to "Malory".toJexl(),
            "last" to "Archer".toJexl(),
            "age" to 75.toJexl()),
        JexlObject(
            "first" to "Malory".toJexl(),
            "last" to "Archer".toJexl(),
            "age" to 33.toJexl())
    )
)
Expression Result
employees[.age >= 30 && .age < 40] [{first=Sterling, last=Archer, age=36}, {first=Malory, last=Archer, age=33}]
employees[.age >= 30 && .age < 90][.age < 37] [{first=Malory, last=Archer, age=33}]

License

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/