A query language for JSON and a template engine to generate text output.
https://mvnrepository.com/artifact/com.octomix.josson/josson
<dependency>
<groupId>com.octomix.josson</groupId>
<artifactId>josson</artifactId>
<version>1.5.0</version>
</dependency>
implementation group: 'com.octomix.josson', name: 'josson', version: '1.5.0'
Operator | Description |
---|---|
( | Grouping |
) | Grouping |
= | Is equal to (support object and array) |
!= | Not equal to (support object and array) |
> | Greater than |
>= | Greater than or equal to |
< | Less than |
<= | Less than or equal to |
=~ | Left matches regular expression |
!=~ | Not match regular expression |
! | Logical NOT |
& | Logical AND |
| | Logical OR |
Operator | Description |
---|---|
>=< | Inner join |
<=< | Left join one |
>=> | Right join one |
<=<< | Left join many |
>>=> | Right join many |
<!< | Left excluding join |
>!> | Right excluding join |
<!> | Outer excluding join |
<+< | Left concatenate |
>+> | Right concatenate |
<-< | Subtract right from left |
>-> | Subtract left from right |
<-> | Symmetric difference |
<u> | Union |
>n< | Intersection |
| | Chaining pipe |
<==> | Equals (returns BooleanNode) |
<!=> | Not equals (returns BooleanNode) |
Initial setup for date time formatting and JSON serialization.
Josson.setLocale(Locale.ENGLISH); // default Locale.getDefault()
Josson.setZoneId(ZoneId.of("Asia/Hong_Kong")); // default ZoneId.systemDefault()
Josson.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Josson support multithreading for array elements manipulation.
When thread pool size is 2 or above, a little more system resource is required to retain the array order.
Turn it off if the array elements order of the result is negligible.
Josson.setMinArraySizeToUseMultiThread(200); // default size is 100
Josson.setThreadPoolSize(2); // default size is 4
Josson.setRetainArrayOrder(false); // default ture
To create a Josson object from a Jackson JsonNode.
Josson josson = Josson.create(jsonNode);
To create a Josson object from a Java object.
Josson josson = Josson.from(object);
To create a Josson object from a JSON string.
Josson josson = Josson.fromJsonString("...");
To apply a Josson query path and get the result JsonNode.
JsonNode node = josson.getNode(expression);
Define initial variables for a query. Variable name must start with “$”.
Map<String, JsonNode> vars = new HashMap<>();
vars.put("$a", IntNode.valueOf(3));
JsonNode node = josson.getNode("concat('qty=',$a)", vars);
To apply a Josson query path and get the path trace along the main branch.
The trace object contains all progressive nodes and variables defined along the main branch.
PathTrace trace = josson.getPathTrace(expression);
A Josson Path is constructed with Path Steps connected by .
.
A path step can…
Step Syntax | Description |
---|---|
key |
A child element key name |
[number] |
An array element by zero-based index |
[expression] |
A boolean filter expression to find the first matching array element |
[expression]* |
A boolean filter expression to query all matching array elements |
[expression]@ |
Filter all matching elements and divert each to separate branches |
[]@ |
Divert each element of the current array node to separate branches |
array@ |
Divert each array element to separate branches |
function() |
A Josson function |
@function() |
Merge all branch results into a single array before manipulation |
function()@ |
Divert the function output array elements to separate branches |
* |
Wildcard search that return the first resolvable non-null result |
** |
Wildcard search that return all object elements |
*@ |
Wildcard search and divert each object element to separate branches |
*(expression) |
Multi-level wildcard search and return the first resolvable element |
*[expression] |
Wildcard search with filter and return the first matching element |
*[expression]* |
Wildcard search with filter and return all matching elements |
*[expression]@ |
Wildcard search with filter and divert each element to separate branches |
~'regex' |
Search by regular expression and return the first matching element |
~'regex'* |
Search by regular expression and return all matching elements |
~'regex'@ |
Search by regular expression and divert each element to separate branches |
To specify an array and then apply an index or a filter can be simplified by removing the .
between them.
The following two statements produce the same final result. But have different number of path steps.
The first one contains two path steps, select an array and then filter.
The second one contains one path step only, filter an array directly.
array.[expression]*
array[expression]*
Filter can also apply to an object node.
If the expression is evaluated to true
, the object itself is returned.
Otherwise, return null
.
Example
{
"a": {
"b": {
"c": [1, 2, "d"]
},
"x": {
"y": "z"
}
}
}
a.b.c ==> [1, 2, "d"]
a.b.c[0] ==> 1
a.b.c[2].upperCase() ==> "D"
a.b.c[isNumber()]* ==> [ 1, 2 ]
a.x[y='z'] ==> { "y": "z" }
a.x[y='1'] ==> null
a.*.y ==> "z"
Enclose the key name with double quote if it contains “.” or starts/ends with spaces.
Example
{
"a.b": {
" c.d ": 123
}
}
"a.b"." c.d " ==> 123
Wildcard search and regular expression search path steps work on object node.
Multi-level wildcard searches for the first resolvable result on path steps in order.
No argument or an argument of value 0
means unlimited levels.
Example
// Search for *.y
*(1).y ==> null
// Search for *.y and then *.*.y
*(2).y ==> "z"
// Unlimited levels
*(0).y ==> "z"
*().y ==> "z"
A wildcard search with filter is actually a combination of 3 steps.
The following query is the same as a wildcard search with filter that returns all matching elements:
Josson function entries()
> Find-all array filter [expression]*
> Select element value
// *[expression]* is expanded to...
entries().[expression]*.value
Function entries()
transform object { "name" : <JsonNode>, ... }
into this new structure:
[
{
"key" : "name",
"value" : <JsonNode>
},
:
:
]
Therefore, use keyword key
in a wildcard filter expression to search.
Even keyword value
can also be used in wildcard filter expression.
Example
*[key.startsWith('item') & value.isText()]
*[key.matches('^[A-Z]{10}$')]*
*[key =~ '^[A-Z]{10}$']*
entries().[key =~ '^[A-Z]{10}$']*.value
The last 3 examples do the same thing and can be simplified to this syntax:
~'^[A-Z]{10}$'*
Additional step symbols are available in filter expression and function argument.
Step | Operation | Description |
---|---|---|
$ |
N/A | Restart from the root node |
.. |
N/A | Node of the previous step (each additional dot go back one more step) |
... |
N/A | Node of the previous step’s previous step |
$letVar |
N/A | Variable defined by function let() |
? |
Aggregate | Current array node |
? |
Scalar | Current non-array node or current array node’s each element |
@ |
Scalar | Current array node |
# |
Scalar | Zero-based index of an array element |
## |
Scalar | One-based index of an array element |
#A |
Scalar | Uppercase alphabetic index of an array element |
#a |
Scalar | Lowercase alphabetic index of an array element |
#R |
Scalar | Uppercase roman numerals index of an array element |
#r |
Scalar | Lowercase roman numerals index of an array element |
Exception
Function let()
and option setting functions are not counted as a path step.
Josson path chart shows data type changes and data flow along the path.
Data filtering, transformation and formatting details are not included.
Element | Description |
---|---|
→ |
A progress step |
⇒ |
An end result |
"" |
A text node |
$I |
An integer node |
$D |
A double node |
$TF |
A boolean node |
{} |
An object node |
{=} |
An object validator |
[] |
An array node |
[]@ |
Divert each array element to separate branches |
[#] |
An indexed array element |
[=] |
A find-first filter |
[=]* |
A find-all filter |
[=]@ |
A find-all filter and divert each element to separate branches |
/ @ \ |
Divert to separate branches |
\ @ → [] / |
Merge branches into an array |
func() |
A Josson function |
(%) |
Function argument, the current object’s child node |
(?) |
Scalar function argument, the current non-array node itself or array node’s each element |
(?[]) |
Aggregate function argument, the current array node |
(@) |
Scalar function argument, the current array node |
!! |
The position where the step is unresolvable |
Below is the JSON for this tutorial.
{
"salesOrderId": "SO0001",
"salesDate": "2022-01-01T10:01:23",
"salesPerson": "Raymond",
"customer": {
"customerId": "CU0001",
"name": "Peggy",
"phone": "+852 62000610"
},
"items": [
{
"itemCode": "B00001",
"name": "WinWin TShirt Series A - 2022",
"brand": "WinWin",
"property": {
"size": "M",
"colors": [ "WHITE", "RED" ]
},
"qty": 2,
"unit": "Pcs",
"unitPrice": 15.0,
"tags": [ "SHIRT", "WOMEN" ]
},
{
"itemCode": "A00308",
"name": "OctoPlus Tennis Racket - Star",
"brand": "OctoPlus",
"property": {
"colors": [ "BLACK" ]
},
"qty": 1,
"unit": "Pcs",
"unitPrice": 150.0,
"unitDiscount": 10.0,
"tags": [ "TENNIS", "SPORT", "RACKET" ]
},
{
"itemCode": "A00201",
"name": "WinWin Sport Shoe - Super",
"brand": "WinWin",
"property": {
"size": "35",
"colors": [ "RED" ]
},
"qty": 1,
"unit": "Pair",
"unitPrice": 110.0,
"unitDiscount": 10.0,
"tags": [ "SHOE", "SPORT", "WOMEN" ]
}
],
"totalAmount": 270.0
}
To query a value node.
josson.getNode("salesPerson")
==>
"Raymond"
Path chart
{} → salesPerson ⇒ ""
Node name is case-sensitive. Josson returns null value if the path is unresolvable.
josson.getNode("salesperson")
==>
!unresolvable!
Path chart
{} → salesperson!! ⇒ !unresolvable!
To query an object node.
josson.getNode("customer")
==>
{
"customerId" : "CU0001",
"name" : "Peggy",
"phone" : "+852 62000610"
}
Path chart
{} → customer{} ⇒ {}
Parent node and child node are connected by a .
.
josson.getNode("customer.name")
==>
"Peggy"
Path chart
{} → customer{} → name ⇒ ""
Function is constructed by a function name followed by parentheses with optional comma-separated arguments.
A function manipulate the current node and produce an output along the path.
josson.getNode("customer.name.upperCase()")
==>
"PEGGY"
Path chart
{} → customer{} → name → upperCase(?) ⇒ ""
Function name is case-insensitive.
If one more parameter is given in addition to a function’s maximum number of argument,
the function will manipulate the data that evaluated from the 1st parameter.
This mechanism does not apply to function that accept unlimited number of arguments.
e.g. upperCase() needs 0 argument. If 1 parameter is given, upperCase() will manipulate that data.
josson.getNode("customer.UPPERCase(name)")
==>
"PEGGY"
Path chart
{} → customer{} → upperCase(%) ⇒ ""
If the function is the first path step, it works on the root node.
josson.getNode("upperCase(customer.name)")
==>
"PEGGY"
Path chart
{} → upperCase(%) ⇒ ""
Functions can be nested and the parameters can refer to those child nodes of the same step.
josson.getNode("customer.concat(upperCase(name), ' / ', phone)")
==>
"PEGGY / +852 62000610"
Path chart
{} → customer{} → concat(%) ⇒ ""
A path start with numbers override the data and produces an integer node.
josson.getNode("123")
==>
123
Path chart
$I ⇒ $I
A path start with numbers and has .
produces a double node.
josson.getNode("123.40")
==>
123.4
Path chart
$D ⇒ $D
A path start and end with single quote '
override the data and produces a text string node.
If the string literal contains a single quote, it is replaced by two single quotes.
josson.getNode("'She said, ''Go ahead''.'")
==>
"She said, 'Go ahead'."
Path chart
"" ⇒ ""
A path start with true
or false
override the data and produces a boolean node.
josson.getNode("true.not()")
==>
false
Path chart
$TF → not(?) ⇒ $TF
To query an array node.
josson.getNode("items")
==>
[ {
"itemCode" : "B00001",
"name" : "WinWin TShirt Series A - 2022",
"brand" : "WinWin",
"property" : {
"size" : "M",
"colors" : [ "WHITE", "RED" ]
},
"qty" : 2,
"unit" : "Pcs",
"unitPrice" : 15.0,
"tags" : [ "SHIRT", "WOMEN" ]
}, {
"itemCode" : "A00308",
"name" : "OctoPlus Tennis Racket - Star",
"brand" : "OctoPlus",
"property" : {
"colors" : [ "BLACK" ]
},
"qty" : 1,
"unit" : "Pcs",
"unitPrice" : 150.0,
"unitDiscount" : 10.0,
"tags" : [ "TENNIS", "SPORT", "RACKET" ]
}, {
"itemCode" : "A00201",
"name" : "WinWin Sport Shoe - Super",
"brand" : "WinWin",
"property" : {
"size" : "35",
"colors" : [ "RED" ]
},
"qty" : 1,
"unit" : "Pair",
"unitPrice" : 110.0,
"unitDiscount" : 10.0,
"tags" : [ "SHOE", "SPORT", "WOMEN" ]
} ]
Path chart
{} → items[] ⇒ [{}]
An array filter is enclosed by square brackets.
Directly query an array element by zero-based index value.
josson.getNode("items[0]")
==>
{
"itemCode" : "B00001",
"name" : "WinWin TShirt Series A - 2022",
"brand" : "WinWin",
"property" : {
"size" : "M",
"colors" : [ "WHITE", "RED" ]
},
"qty" : 2,
"unit" : "Pcs",
"unitPrice" : 15.0,
"tags" : [ "SHIRT", "WOMEN" ]
}
Path chart
{} → items[#] ⇒ {}
To query a child value node of an array element.
josson.getNode("items[1].name")
==>
"OctoPlus Tennis Racket - Star"
Path chart
{} → items[#] → {} → name ⇒ ""
To query a child object node of an array element.
josson.getNode("items[2].property")
==>
{
"size" : "35",
"colors" : [ "RED" ]
}
Path chart
{} → items[#] → {} → property{} ⇒ {}
To query all the elements of an array node and output them inside an array node.
josson.getNode("items.qty")
==>
[ 2, 1, 1 ]
Path chart
{} → items[] → [{}] → [qty] ⇒ [$I]
A function that manipulates each array element and output all results inside an array node.
josson.getNode("items.concat('Qty=',qty)")
==>
[ "Qty=2", "Qty=1", "Qty=1" ]
Path chart
{} → items[] → [{}] → [concat(%) ⇒ ""] ⇒ [""]
If a step is working on an object or value node, ?
represents that node.
josson.getNode("items.qty.concat('Qty=',?)")
==>
[ "Qty=2", "Qty=1", "Qty=1" ]
Path chart
{} → items[] → [{}] → [qty] → [concat(?) ⇒ ""] ⇒ [""]
An aggregate function manipulates an array node and produce a value node.
josson.getNode("items.qty.sum()")
==>
4.0
Path chart
{} → items[] → [{}] → [qty] → sum(?[]) ⇒ $D
Uses Java standard formatting pattern.
josson.getNode("items.sum(qty).formatNumber('#,##0')")
==>
"4"
Path chart
{} → items[] → [{}] → sum(?[%]) → $D → formatNumber(?) ⇒ ""
Find the first matching element by array filter.
josson.getNode("items.itemCode[!startsWith('A')]")
==>
"B00001"
Path chart
{} → items[] → [{}] → [itemCode][=] ⇒ ""
Filter using relational operators =
, !=
, >
, >=
, <
and <=
.
josson.getNode("items[unitDiscount > 0].name")
==>
"OctoPlus Tennis Racket - Star"
Path chart
{} → items[=] → {} → name ⇒ ""
Returns null value if nothing matches the array filter.
josson.getNode("items[unitDiscount > 100].name")
==>
!unresolvable!
Path chart
{} → items[=]!! → {} → name ⇒ !unresolvable!
To query all matching elements, add a modifier *
after the array filter.
josson.getNode("items[unitDiscount > 0]*.name")
==>
[ "OctoPlus Tennis Racket - Star", "WinWin Sport Shoe - Super" ]
Path chart
{} → items[=]* → [{}] → [name] ⇒ [""]
If a step is working on an array node, #
denotes the zero-based index of an array element.
josson.getNode("items[#.isEven()]*.itemCode")
==>
[ "B00001", "A00201" ]
Path chart
{} → items[=]* → [{}] → [itemCode] ⇒ [""]
A succession of two path steps that produced a nested array will be flattened automatically.
josson.getNode("items[true]*.tags[true]*")
==>
[ "SHIRT", "WOMEN", "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
Path chart
{} → items[=]* → [{}] → [tags[=]* ⇒ [""]] ⇒ [""]
Path step array.
is the same as array[true]*.
.
josson.getNode("items.tags")
==>
[ "SHIRT", "WOMEN", "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
Path chart
{} → items[] → [{}] → [tags[] ⇒ [""]] ⇒ [""]
To simulate cancellation of the automatic flatten mechanism, add a divert-branch modifier “@” to the end of the first array name.
josson.getNode("[email protected]")
==>
[ [ "SHIRT", "WOMEN" ], [ "TENNIS", "SPORT", "RACKET" ], [ "SHOE", "SPORT", "WOMEN" ] ]
Path chart
{} → tags[] → [""]
/ \
{} → items[] → []@ @ ⇒ [[""]]
\ /
{} → tags[] → [""]
Modifier @
after a path step separator .
merges all branch results into a single array before manipulation.
josson.getNode("items@.@tags")
==>
[ "SHIRT", "WOMEN", "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
Path chart
{}
/ \
{} → items[] → []@ @ ⇒ [{}] → [tags[] ⇒ [""]] ⇒ [""]
\ /
{}
If a step is working on an array node, ?
represents an array element.
=~
matches a regular expression.
josson.getNode("items.tags[? =~ '^S.*O.+']*")
==>
[ "SPORT", "SHOE", "SPORT" ]
Path chart
{} → items[] → [{}] → [tags[=]* ⇒ [""]] ⇒ [""]
The matching criteria supports logical operators and parentheses.
not
!
and&
or|
josson.getNode("items[(unitDiscount=null | unitDiscount=0) & !(qty<=1)]*.name")
==>
[ "WinWin TShirt Series A - 2022" ]
Path chart
{} → items[=]* → [{}] → [name] ⇒ [""]
Example of a find-all filter operation with flattened array result.
josson.getNode("items[tags.contains('SPORT')]*.tags")
==>
[ "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
Path chart
{} → items[=]* → [{}] → [tags[] ⇒ [""]] ⇒ [""]
An array filter modifier @
divert each element to separate branch for upcoming manipulation.
The final output merges branches into an array.
josson.getNode("items[tags.containsIgnoreCase('Women')]@.tags")
==>
[ [ "SHIRT", "WOMEN" ], [ "SHOE", "SPORT", "WOMEN" ] ]
Path chart
{} → tags[] → [""]
/ \
{} → items[=]@ @ ⇒ [[""]]
\ /
{} → tags[] → [""]
Aggregate functions work on an array node and produce a value node.
josson.getNode("items.tags.join('+')")
==>
SHIRT+WOMEN+TENNIS+SPORT+RACKET+SHOE+SPORT+WOMEN
Path chart
{} → items[] → [{}] → [tags[] ⇒ [""]] → [""] → join(?[]) ⇒ ""
An array node can apply the modifier @
that divert each element to separate branch.
josson.getNode("[email protected]('+')")
==>
[ "SHIRT+WOMEN", "TENNIS+SPORT+RACKET", "SHOE+SPORT+WOMEN" ]
Path chart
{} → tags[] → [""] → join(?[]) → ""
/ \
{} → items[] → []@ @ ⇒ [""]
\ /
{} → tags[] → [""] → join(?[]) → ""
Syntax []@
diverts each element of the current array node.
josson.getNode("items.join([]@.tags.join('+'),' / ')")
==>
"SHIRT+WOMEN / TENNIS+SPORT+RACKET / SHOE+SPORT+WOMEN"
Path chart
{} → tags[] → [""] → join(?[]) → ""
/ \
{} → items[] → [{}] → join(?[]@ @ ⇒ [""]) ⇒ ""
\ /
{} → tags[] → [""] → join(?[]) → ""
Modifier @
before a function name merges all branch results into a single array before manipulation.
josson.getNode("[email protected]('+').@join(' / ')")
==>
"SHIRT+WOMEN / TENNIS+SPORT+RACKET / SHOE+SPORT+WOMEN"
Path chart
{} → tags[] → [""] → join(?[]) → ""
/ \
{} → items[] → []@ @ → [""] → join(?[]) ⇒ ""
\ /
{} → tags[] → [""] → join(?[]) → ""
Modifier @
after a function diverts the function output array elements to separate branches.
It has the same effect of a path step .[]@
after a function.
josson.getNode("'1+2 | 3+4 | 5+6'.split('|')@.split('+').calc(?*2).round(0).join('+').concat('(',?,')/2').@join(' | ')")
==>
"(2+4)/2 | (6+8)/2 | (10+12)/2"
Path chart
"" → split(?) → [""] → [calc(?) ⇒ $D] → [round(?) ⇒ $I] → join(?[]) → "" → concat(?) → ""
/ \
"" → split(?) → [""]@ @ → [""] → join(?[]) ⇒ ""
\ /
"" → split(?) → [""] → [calc(?) ⇒ $D] → [round(?) ⇒ $I] → join(?[]) → "" → concat(?) → ""
All function parameters can refer to a child node of the step.
josson.getNode("[email protected](concat('[',brand,'] ',name,'\n'), qty).@join()")
==>
"[WinWin] WinWin TShirt Series A - 2022\n" +
"[WinWin] WinWin TShirt Series A - 2022\n" +
"[OctoPlus] OctoPlus Tennis Racket - Star\n" +
"[WinWin] WinWin Sport Shoe - Super\n"
Path chart
{} → repeat(%) → ""
/ \
{} → items[] → []@ @ → [""] → join(?[]) ⇒ ""
\ /
{} → repeat(%) → ""
Scalar functions work on array and produce an array, such as concat()
, manipulate on each element.
josson.getNode("items.concat('Item ',#,': [',itemCode,'] ',qty,unit,' x ',name,' <',property.colors.join(','),'>').join('\n')")
==>
"Item 0: [B00001] 2Pcs x WinWin TShirt Series A - 2022 <WHITE,RED>\n" +
"Item 1: [A00308] 1Pcs x OctoPlus Tennis Racket - Star <BLACK>\n" +
"Item 2: [A00201] 1Pair x WinWin Sport Shoe - Super <RED>"
Path chart
{} → items[] → [{}] → [concat(%) ⇒ ""] → join(?[]) ⇒ ""
If a step is working on an array node, @
represents that array node.
##
denotes the one-based index of an array element.
josson.getNode("items.sort(itemCode).concat('Item ',##,'/',@.size(),': [',itemCode,'] ',qty,unit,' x ',name,' <',property.colors.join(','),'>').join('\n')")
==>
"Item 1/3: [A00201] 1Pair x WinWin Sport Shoe - Super <RED>\n" +
"Item 2/3: [A00308] 1Pcs x OctoPlus Tennis Racket - Star <BLACK>\n" +
"Item 3/3: [B00001] 2Pcs x WinWin TShirt Series A - 2022 <WHITE,RED>"
Path chart
{} → items[] → [{}] -> sort(%) → [{}] → [concat(@, %) ⇒ ""] → join(?[]) ⇒ ""
An object node with a validation filter.
josson.getNode("customer[name='Peggy']")
==>
{
"customerId" : "CU0001",
"name" : "Peggy",
"phone" : "+852 62000610"
}
Path chart
{} → customer{=} ⇒ {}
An object node that cannot meet the validation filter criteria returns null.
josson.getNode("customer[name='Raymond']")
==>
!unresolvable!
Path chart
{} → customer{=}!! ⇒ !unresolvable!
In filter expression and function argument, a path starts with symbol “$” restart from the root node.
josson.getNode("items.concat($.customer.customerId, '-', itemCode)")
==>
[ "CU0001-B00001", "CU0001-A00308", "CU0001-A00201" ]
Path chart
.------------- → --------------.
| |
{} → items[] → [{}] → [concat(%,$) ⇒ ""] ⇒ [""]
In filter expression and function argument, a path starts with symbol “…” go back to the previous step’s node.
Each additional dot go back one more step.
josson.getNode("items.property.concat(...customer.name, ' items=', ..size(), ' colors=', colors.join(','))")
==>
[ "Peggy items=3 colors=WHITE,RED", "Peggy items=3 colors=BLACK", "Peggy items=3 colors=RED" ]
Path chart
.---------------------------- → ---------------.
| .------------ → ------------. |
| | | |
{} → items[] → [{}] → [property] → [concat(%,..,...) ⇒ ""] ⇒ [""]
One more example.
josson.getNode("[email protected](....customer.name, ' ', ..itemCode, ' colors=', colors.join(','))")
==>
[ "Peggy items=3 colors=WHITE,RED", "Peggy items=3 colors=BLACK", "Peggy items=3 colors=RED" ]
Path chart
.------------------------------ → --------------.
| .----------- → -----------. |
| | | |
| {} → property{} → concat(%,..,....) → ""
| / \
{} → items[] → []@ @ ⇒ [""]
\ /
{} → property{} → concat(%,..,....) → ""
Function json()
parse a JSON string.
josson.getNode("json('[1,2,\"3\"]')")
==>
[ 1, 2, "3" ]
Path chart
json("") ⇒ []
Relational operator =
and !=
support object comparison.
josson.getNode("[customer = json('{\"name\":\"Peggy\",\"phone\":\"+852 62000610\",\"customerId\":\"CU0001\"}')].isNotNull()")
==>
true
Path chart
{} → {=} → {} → isNotNull(?) ⇒ $TF
Relational operator =
and !=
support root level array values comparison where the position ordering is allowed to be different.
josson.getNode("[items[0].property.colors = json('[\"RED\",\"WHITE\"]')].isNotNull()")
==>
true
Path chart
{} → {=} → {} → isNotNull(?) ⇒ $TF
Function calc()
uses MathParser.org-mXparser library http://mathparser.org/ to perform calculation.
josson.getNode("items.calc(qty * (unitPrice-unitDiscount)).concat(##,'=',?)")
==>
[ null, "2=140.0", "3=100.0" ]
Path chart
{} → items[] → [{}] → [calc(%) ⇒ $D] → [concat(?) ⇒ ""] ⇒ [""]
Scalar functions preserve null element.
josson.getNode("items.calc(qty * (unitPrice-unitDiscount)).[##<=2]*.concat(##,'=',?)")
==>
[ null, "2=140.0" ]
Path chart
{} → items[] → [{}] → [calc(%) ⇒ $D] → [=]* → [concat(?) ⇒ ""] ⇒ [""]
An array-to-value transformation function throws away null nodes automatically.
josson.getNode("items.calc(qty * (unitPrice-unitDiscount)).concat(##,'=',?).join(' / ')")
==>
"2=140.0 / 3=100.0"
Path chart
{} → items[] → [{}] → [calc(%) ⇒ $D] → [concat(?) ⇒ ""] → join(?[]) ⇒ ""
Array filter can filter out null nodes.
josson.getNode("items.calc(qty * (unitPrice-unitDiscount)).[isNotNull()]*.concat(##,'=',?)")
==>
[ "1=140.0", "2=100.0" ]
Path chart
{} → items[] → [{}] → [calc(%) ⇒ $D] → [=]* → [concat(?) ⇒ ""] ⇒ [""]
An argument #A
denotes the uppercase alphabetic array index.
josson.getNode("items.calc(qty * (unitPrice-unitDiscount)).[?!=null]*.concat(#A,'=',?).join(' / ')")
==>
"A=140.0 / B=100.0"
Path chart
{} → items[] → [{}] → [calc(%) ⇒ $D] → [=]* → [concat(?) ⇒ ""] → join(?[]) ⇒ ""
Merge Diverted branches throws away null nodes automatically.
An argument #a
denotes the lowercase alphabetic array index.
josson.getNode("[email protected](qty * (unitPrice-unitDiscount)).@concat(#a,'=',?)")
==>
[ "a=140.0", "b=100.0" ]
Path chart
{} → calc(%) → $D
/ \
{} → items[] → []@ @ → [$D] → [concat(?) ⇒ ""] ⇒ [""]
\ /
{} → calc(%) → $D
mXparser expression accepts single-step path only.
To apply multi-steps path, function or filter, append arguments with syntax newVariable:path
.
josson.getNode("items.calc(qty * (unitPrice-x), x:coalesce(unitDiscount,0)).formatNumber('US$#,##0.00')")
==>
[ "US$30.00", "US$140.00", "US$100.00" ]
Path chart
{} → items[] → [{}] → [calc(%) ⇒ $D] → [formatNumber(?) ⇒ ""] ⇒ [""]
An argument #r
and #R
denotes the lowercase and uppercase roman numerals array index.
josson.getNode("items.unitPrice.calc(? * 2).concat(#r,'=',?)")
==>
[ "i=30.0", "ii=300.0", "iii=220.0" ]
Path chart
{} → items[] → [{}] → [unitPrice ⇒ $D] → [calc(?) ⇒ $D] → [concat(?) ⇒ ""] ⇒ [""]
Function entries()
returns an array of an object’s string-keyed property [{key, value}]
pairs.
josson.getNode("items[0].entries()")
==>
[ {
"key" : "itemCode",
"value" : "B00001"
}, {
"key" : "name",
"value" : "WinWin TShirt Series A - 2022"
}, {
"key" : "brand",
"value" : "WinWin"
}, {
"key" : "property",
"value" : {
"size" : "M",
"colors" : [ "WHITE", "RED" ]
}
}, {
"key" : "qty",
"value" : 2
}, {
"key" : "unit",
"value" : "Pcs"
}, {
"key" : "unitPrice",
"value" : 15.0
}, {
"key" : "tags",
"value" : [ "SHIRT", "WOMEN" ]
} ]
Path chart
{} → items[#] → {} → entries(?) ⇒ [{}]
Function keys()
lists an object’s key names.
josson.getNode("keys()")
==>
[ "salesOrderId", "salesDate", "salesPerson", "customer", "items", "totalAmount" ]
Path chart
{} → keys(?) ⇒ [""]
keys()
can retrieve nested child object keys for a given levels.
josson.getNode("keys(2)")
==>
[ "salesOrderId", "salesDate", "salesPerson", "customer", "customerId", "name", "phone", "items", "totalAmount" ]
Path chart
{} → keys(?) ⇒ [""]
Function collect()
puts all argument values into an array.
Function wrap()
is equivalent to collect(?)
which is wrap the node inside an array.
josson.getNode("collect(salesDate, customer, items.itemCode)")
==>
[ "2022-01-01T10:01:23", {
"customerId" : "CU0001",
"name" : "Peggy",
"phone" : "+852 62000610"
}, [ "B00001", "A00308", "A00201" ] ]
Path chart
{} → collect(%) ⇒ []
Function cumulateCollect()
require 2 arguments.
The 1st parameter is a query to evaluate a result that will be collected into an array.
The 2nd parameter is a query to evaluate the next dataset that loop back for the 1st parameter evaluation again.
The operation loop will be stopped when the next dataset is null.
josson.getNode("json('{\"id\":1,\"val\":11,\"item\":{\"id\":2,\"val\":22,\"item\":{\"id\":3,\"val\":33,\"item\":{\"id\":4,\"val\":44}}}}')" +
".cumulateCollect(map(id,val.calc(?*2)), item)")
==>
[ {
"id" : 1,
"val" : 22.0
}, {
"id" : 2,
"val" : 44.0
}, {
"id" : 3,
"val" : 66.0
}, {
"id" : 4,
"val" : 88.0
} ]
Path chart
{} → cumulateCollect(%) ⇒ [{}]
Function toArray()
puts an object’s values into an array.
josson.getNode("customer.toArray()")
==>
[ "CU0001", "Peggy", "+852 62000610" ]
Path chart
{} → customer{} → toArray(?) ⇒ [""]
Furthermore, function toArray()
puts all arguments (values, object’s values, array elements) into a single array.
josson.getNode("toArray('Hello',customer,items.itemCode.sort())")
==>
[ "Hello", "CU0001", "Peggy", "+852 62000610", "A00201", "A00308", "B00001" ]
Path chart
{} → toArray(%) ⇒ [""]
Function map()
constructs a new object node.
For multi-steps path, the last element name will become the new element name.
To rename an element, use syntax newFieldName:path
or queryThatResolveToName::path
.
josson.getNode("map(customer.name,date:salesDate,sales:map(items.concat(name,' x ',qty,unit), totalQty:items.sum(qty), totalAmount))")
==>
{
"name" : "Peggy",
"date" : "2022-01-01T10:01:23",
"sales" : {
"items" : [ "WinWin TShirt Series A - 2022 x 2Pcs", "OctoPlus Tennis Racket - Star x 1Pcs", "WinWin Sport Shoe - Super x 1Pair" ],
"totalQty" : 4.0,
"totalAmount" : 270.0
}
}
Path chart
{} → map(%) ⇒ {}
Function field()
adds, removes and renames field on the current object node.
To remove an element, use syntax fieldName:
or queryThatResolveToName::
.
josson.getNode("items[0].field(subtotal:calc(qty * (unitPrice-x), x:coalesce(unitDiscount,0)),brand:,property:,tags:)")
==>
{
"itemCode" : "B00001",
"name" : "WinWin TShirt Series A - 2022",
"qty" : 2,
"unit" : "Pcs",
"unitPrice" : 15.0,
"subtotal" : 30.0
}
Path chart
{} → items[#] → {} → field(%) ⇒ {}
Functions map()
and field()
works on array.
josson.getNode("items.field(subtotal:calc(qty * (unitPrice-x), x:coalesce(unitDiscount,0)),brand:,property:,tags:)")
==>
[ {
"itemCode" : "B00001",
"name" : "WinWin TShirt Series A - 2022",
"qty" : 2,
"unit" : "Pcs",
"unitPrice" : 15.0,
"subtotal" : 30.0
}, {
"itemCode" : "A00308",
"name" : "OctoPlus Tennis Racket - Star",
"qty" : 1,
"unit" : "Pcs",
"unitPrice" : 150.0,
"unitDiscount" : 10.0,
"subtotal" : 140.0
}, {
"itemCode" : "A00201",
"name" : "WinWin Sport Shoe - Super",
"qty" : 1,
"unit" : "Pair",
"unitPrice" : 110.0,
"unitDiscount" : 10.0,
"subtotal" : 100.0
} ]
Path chart
{} → items[] → [{}] → [field(%) ⇒ {}] ⇒ [{}]
For functions map()
and field()
, parameter syntax path:+
present an unresolvable path as a NullNode.
josson.getNode("items.map(itemCode, unitDiscount)")
==>
[ {
"itemCode" : "B00001",
}, {
"itemCode" : "A00308",
"unitDiscount" : 10.0
}, {
"itemCode" : "A00201",
"unitDiscount" : 10.0
} ]
josson.getNode("items.map(itemCode, unitDiscount:+)")
==>
[ {
"itemCode" : "B00001",
"unitDiscount" : null
}, {
"itemCode" : "A00308",
"unitDiscount" : 10.0
}, {
"itemCode" : "A00201",
"unitDiscount" : 10.0
} ]
josson.getNode("items.map(itemCode, unitDiscount:if([unitDiscount=null],null,unitDiscount))")
==>
[ {
"itemCode" : "B00001",
"unitDiscount" : null
}, {
"itemCode" : "A00308",
"unitDiscount" : 10.0
}, {
"itemCode" : "A00201",
"unitDiscount" : 10.0
} ]
For functions map()
and field()
, parameter syntax newFieldName:+path
present an unresolvable path as a NullNode with a new field name.
josson.getNode("items.map(itemCode, discount:+unitDiscount)")
==>
[ {
"itemCode" : "B00001",
"discount" : null
}, {
"itemCode" : "A00308",
"discount" : 10.0
}, {
"itemCode" : "A00201",
"discount" : 10.0
} ]
For functions map()
and field()
, parameter syntax **:object
extracts all the fields within a given object.
josson.getNode("items.slice(0,2).field(**:properties, properties:)")
==>
[ {
"itemCode" : "B00001",
"name" : "WinWin TShirt Series A - 2022",
"brand" : "WinWin",
"qty" : 2,
"unit" : "Pcs",
"unitPrice" : 15.0,
"tags" : [ "SHIRT", "WOMEN" ],
"size" : "M",
"colors" : [ "WHITE", "RED" ]
}, {
"itemCode" : "A00308",
"name" : "OctoPlus Tennis Racket - Star",
"brand" : "OctoPlus",
"qty" : 1,
"unit" : "Pcs",
"unitPrice" : 150.0,
"unitDiscount" : 10.0,
"tags" : [ "TENNIS", "SPORT", "RACKET" ],
"colors" : [ "BLACK" ]
} ]
Function group()
works like SQL group by
. It will build a structure of [{key, [elements]}]
.
The first parameter is the grouping key. If it is a function, it will be given a name key
in the output.
The optional second parameter is to evaluate the grouped element. The default is the whole array element.
And the default output array name is elements
.
The names can be renamed by preceding with newName:
or queryThatResovleToName::
.
josson.getNode("items.group(brand,map(name,qty,netPrice:calc(unitPrice-x,x:coalesce(unitDiscount,0))))")
==>
[ {
"brand" : "WinWin",
"elements" : [ {
"name" : "WinWin TShirt Series A - 2022",
"qty" : 2,
"netPrice" : 15.0
}, {
"name" : "WinWin Sport Shoe - Super",
"qty" : 1,
"netPrice" : 100.0
} ]
}, {
"brand" : "OctoPlus",
"elements" : [ {
"name" : "OctoPlus Tennis Racket - Star",
"qty" : 1,
"netPrice" : 140.0
} ]
} ]
josson.getNode(
"items.group(brand,map(name,qty,netPrice:calc(unitPrice-x,x:coalesce(unitDiscount,0))))@" +
".concat('Brand : ',brand,'\n',elements.concat('- ',name,' : Qty=',qty,' Amt=',calc(qty*netPrice),'\n').join()," +
"'> Sub-total : Qty=',elements.sum(qty),' Amt=',elements.sum(calc(qty*netPrice))).@join('\n\n')")
==>
Brand : WinWin
- WinWin TShirt Series A - 2022 : Qty=2 Amt=30.0
- WinWin Sport Shoe - Super : Qty=1 Amt=100.0
> Sub-total : Qty=3.0 Amt=130.0
Brand : OctoPlus
- OctoPlus Tennis Racket - Star : Qty=1 Amt=140.0
> Sub-total : Qty=1.0 Amt=140.0
Path chart
{} → concat(%) → ""
/ \
{} → items[] → [{}] → group(%) → [{}]@ @ → [""] → join(?[]) ⇒ ""
\ /
{} → concat(%) → ""
Function unwind()
works like MongoDB $unwind
operation. The operation is the reverse of group()
.
It supports parameter with prefix +
in order to add element even the path is unresolvable.
josson.getNode("items.group(brand,map(name,qty,netPrice:calc(unitPrice-x,x:coalesce(unitDiscount,0)))).unwind(elements)")
==>
[ {
"brand" : "WinWin",
"name" : "WinWin TShirt Series A - 2022",
"qty" : 2,
"netPrice" : 15.0
}, {
"brand" : "WinWin",
"name" : "WinWin Sport Shoe - Super",
"qty" : 1,
"netPrice" : 100.0
}, {
"brand" : "OctoPlus",
"name" : "OctoPlus Tennis Racket - Star",
"qty" : 1,
"netPrice" : 140.0
} ]
Path chart
{} → items[] → [{}] → group(%) → [{}] → unwind(%) ⇒ [{}]
Function flatten()
flatten an array for a given number of times.
josson.getNode("[email protected]")
==>
[ [ "SHIRT", "WOMEN" ], [ "TENNIS", "SPORT", "RACKET" ], [ "SHOE", "SPORT", "WOMEN" ] ]
flatten([email protected], 1, null)
==>
[ "SHIRT", "WOMEN", "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
Function flatten()
flatten an array same as the default path step behavior. But more readable.
josson.getNode("[email protected].@.[true]*")
==>
[ "SHIRT", "WOMEN", "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
josson.getNode("[email protected][email protected](1)")
==>
[ "SHIRT", "WOMEN", "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
Path chart
{} → tags[] → [""]
/ \
{} → items[] → []@ @ → [[""]] → flatten() ⇒ [""]
\ /
{} → tags[] → [""]
If the parameter value of flatten()
is textual, it will act as a key name separator to build a flattened object.
If the separator is null
, the standalone end node name will be used instead.
The second parameter is optional and is the string format for array index, usually use with this combination ('.', '[%d]')
.
josson.getNode("flatten('_')")
==>
{
"salesOrderId" : "SO0001",
"salesDate" : "2022-01-01T10:01:23",
"salesPerson" : "Raymond",
"customer_customerId" : "CU0001",
"customer_name" : "Peggy",
"customer_phone" : "+852 62000610",
"items_0_itemCode" : "B00001",
"items_0_name" : "WinWin TShirt Series A - 2022",
"items_0_brand" : "WinWin",
"items_0_property_size" : "M",
"items_0_property_colors_0" : "WHITE",
"items_0_property_colors_1" : "RED",
"items_0_qty" : 2,
"items_0_unit" : "Pcs",
"items_0_unitPrice" : 15.0,
"items_0_tags_0" : "SHIRT",
"items_0_tags_1" : "WOMEN",
"items_1_itemCode" : "A00308",
"items_1_name" : "OctoPlus Tennis Racket - Star",
"items_1_brand" : "OctoPlus",
"items_1_property_colors_0" : "BLACK",
"items_1_qty" : 1,
"items_1_unit" : "Pcs",
"items_1_unitPrice" : 150.0,
"items_1_unitDiscount" : 10.0,
"items_1_tags_0" : "TENNIS",
"items_1_tags_1" : "SPORT",
"items_1_tags_2" : "RACKET",
"items_2_itemCode" : "A00201",
"items_2_name" : "WinWin Sport Shoe - Super",
"items_2_brand" : "WinWin",
"items_2_property_size" : "35",
"items_2_property_colors_0" : "RED",
"items_2_qty" : 1,
"items_2_unit" : "Pair",
"items_2_unitPrice" : 110.0,
"items_2_unitDiscount" : 10.0,
"items_2_tags_0" : "SHOE",
"items_2_tags_1" : "SPORT",
"items_2_tags_2" : "WOMEN",
"totalAmount" : 270.0
}
Function unflatten()
reverse the operation of flatten()
.
josson.getNode("items[1].flatten('_').unflatten('_')")
==>
{
"itemCode" : "A00308",
"name" : "OctoPlus Tennis Racket - Star",
"brand" : "OctoPlus",
"property" : {
"colors" : [ "BLACK" ]
},
"qty" : 1,
"unit" : "Pcs",
"unitPrice" : 150.0,
"unitDiscount" : 10.0,
"tags" : [ "TENNIS", "SPORT", "RACKET" ]
}
Functions map()
,field()
,group()
,unwind()
- key name support evaluation using syntax keyQuery::valueQuery
.
josson.getNode("items.map(itemCode::qty)")
==>
[ {
"B00001" : 2
}, {
"A00308" : 1
}, {
"A00201" : 1
} ]
Path chart
{} → items[] → [{}] → [map(%) ⇒ {}] ⇒ [{}]
Syntax keyQuery::+valueQuery
present an unresolvable path as a NullNode.
josson.getNode("items.map(itemCode::unitDiscount)")
==>
[ { }, {
"A00308" : 10.0
}, {
"A00201" : 10.0
} ]
josson.getNode("items.map(itemCode::+unitDiscount)")
==>
[ {
"B00001" : null
}, {
"A00308" : 10.0
}, {
"A00201" : 10.0
} ]
Function mergeObjects()
merge all objects in an array into one object.
josson.getNode("mergeObjects(customer, items.map(itemCode::qty))")
==>
{
"customerId" : "CU0001",
"name" : "Peggy",
"phone" : "+852 62000610",
"B00001" : 2,
"A00308" : 1,
"A00201" : 1
}
Path chart
{} → mergeObjects(%) ⇒ {}
Function assort()
separates an object’s entries according to different path conditions in sequence,
and put them into the corresponding array if the evaluated result is not null.
Entries will be removed if no condition can be matched.
If the last argument is ??
, each of the remaining entry will be added to the end of result array separately.
If no argument is provided, each entry will be added to the result array separately.
josson.getNode("json('{\"xy1\": 1,\"xy2\": 2,\"ab1\": 3,\"ab2\": 4,\"ab3\": 5,\"zz1\": 6,\"xy3\": 7,\"zz2\": 9,\"zz3\": {\"k\":10}}}')" +
".assort(*.[isEven()], ~'xy.*', ~'ab.*', *)")
==>
[ {
"xy2" : 2,
"ab2" : 4,
"zz1" : 6
}, {
"xy1" : 1,
"xy3" : 7
}, {
"ab1" : 3,
"ab3" : 5
}, {
"zz2" : 9,
"zz3" : {
"k" : 10
}
} ]
josson.getNode("json('{\"xy1\": 1,\"xy2\": 2,\"ab1\": 3,\"ab2\": 4,\"ab3\": 5,\"zz1\": 6,\"xy3\": 7,\"zz2\": 9,\"zz3\": {\"k\":10}}')" +
".assort(*.[isEven()], ~'xy.*', ~'ab.*')")
==>
[ {
"xy2" : 2,
"ab2" : 4,
"zz1" : 6
}, {
"xy1" : 1,
"xy3" : 7
}, {
"ab1" : 3,
"ab3" : 5
} ]
josson.getNode("json('{\"xy1\": 1,\"xy2\": 2,\"ab1\": 3,\"ab2\": 4,\"ab3\": 5,\"zz1\": 6,\"xy3\": 7,\"zz2\": 9,\"zz3\": {\"k\":10}}}')" +
".assort(*.[isEven()], ~'xy.*', ~'ab.*', ??)")
==>
[ {
"xy2" : 2,
"ab2" : 4,
"zz1" : 6
}, {
"xy1" : 1,
"xy3" : 7
}, {
"ab1" : 3,
"ab3" : 5
}, {
"zz2" : 9
}, {
"zz3" : {
"k" : 10
}
} ]
josson.getNode("json('{\"xy1\": 1,\"xy2\": 2,\"ab1\": 3,\"ab2\": 4,\"ab3\": 5,\"zz1\": 6,\"xy3\": 7,\"zz2\": 9,\"zz3\": {\"k\":10}}}')" +
".assort()")
==>
[ {
"xy1" : 1
}, {
"xy2" : 2
}, {
"ab1" : 3
}, {
"ab2" : 4
}, {
"ab3" : 5
}, {
"zz1" : 6
}, {
"xy3" : 7
}, {
"zz2" : 9
}, {
"zz3" : {
"k" : 10
}
} ]
Function assort()
also works for array. The result is an array of arrays.
josson.getNode("json('[1,2,3,4,5,6,7,8,9,10,11,12]').assort([?<5], [isEven()], [?<9], ?)")
==>
[ [ 1, 2, 3, 4 ], [ 6, 8, 10, 12 ], [ 5, 7 ], [ 9, 11 ] ]
josson.getNode("json('[1,2,3,4,5,6,7,8,9,10,11,12]').assort([?<5], [isEven()], [?<9], ??)")
==>
[ [ 1, 2, 3, 4 ], [ 6, 8, 10, 12 ], [ 5, 7 ], [ 9 ], [ 11 ] ]
Function eval()
evaluates the value of a text node as a query statement.
josson.getNode("json('{\"a\":1,\"b\":2,\"statement\":\"calc(a+b*2)\"}').eval(statement)")
==>
5.0
josson.getNode("json('[{\"a\":3,\"s\":\"calc(a*2)\"},{\"a\":4,\"s\":\"calc(a*2)\"}]')@.eval(s)")
==>
[ 6.0, 8.0 ]
There are 246 functions. They are classified into 8 categories:
Arithmetic Functions
String Functions
Date Functions
Format Functions
Logical Functions
Array Functions
Structural Functions
Programmable Functions
Following are description of each function.
Returns the absolute value of a number.
-3.14.abs() ==> 3.14
abs(3.14) ==> 3.14
Evaluates a math expression using mXparser library.
1.5.calc(? * 2 + ? / 2) ==> 3.75
calc(2^8) ==> 256.0
calc(sqrt(a^2 + b^2), a:3, b:4) ==> 5.0
Rounds a number up to the nearest integer.
3.14.ceil() ==> 4
ceil(-3.14) ==> -3
Rounds a number down to the nearest integer.
3.14.floor() ==> 3
floor(-3.14) ==> -4
Returns the remainder of a division operation.
8.mod(3) ==> 2
8.mod(?, 3) ==> 2
mod(-8, 3) ==> 1
3.mod(-8, ?) ==> 1
Rounds a number to a specified number of decimal places.
3.14.round(1) ==> 3.1
3.14.round(?, 1) ==> 3.1
round(3.56, 0) ==> 4
Abbreviates a String using ellipses.
'abcdefghijkl'.abbreviate(9) ==> "abcdef..."
'abcdefghijkl'.abbreviate(5, 9) ==> "...fgh..."
'abcdefghijkl'.abbreviate(?, 7, 9) ==> "...ghijkl"
abbreviate('abcdefghijkl', 0, 9) ==> "abcdef..."
abbreviate('abcdefghijkl', 1, 9) ==> "abcdef..."
abbreviate('abcdefghijkl', 4, 9) ==> "abcdef..."
abbreviate('abcdefghijkl', 5, 9) ==> "...fgh..."
abbreviate('abcdefghijkl', 6, 9) ==> "...ghijkl"
abbreviate('abcdefghijkl', 10, 9) ==> "...ghijkl"
abbreviate('abcdefghijkl', 11, 9) ==> "...ghijkl"
Appends a string to the end of another string.
'abc'.append('xyz') ==> "abcxyz"
'abc'.append(?, 'xyz') ==> "abcxyz"
append('abcxyz', 'xyz') ==> "abcxyzxyz"
'xyz'.append('abcXYZ', ?) ==> "abcXYZxyz"
Appends the suffix to the end of the string if the string does not already end with the suffix.
'abc'.appendIfMissing('xyz') ==> "abcxyz"
'abc'.appendIfMissing(?, 'xyz') ==> "abcxyz"
appendIfMissing('abcxyz', 'xyz') ==> "abcxyz"
'xyz'.appendIfMissing('abcXYZ', ?) ==> "abcXYZxyz"
Appends the suffix to the end of the string if the string does not already end, case insensitive, with any of the suffixes.
'abc'.appendIfMissingIgnoreCase('xyz') ==> "abcxyz"
'abc'.appendIfMissingIgnoreCase(?, 'xyz') ==> "abcxyz"
appendIfMissingIgnoreCase('abcxyz', 'xyz') ==> "abcxyz"
'xyz'.appendIfMissingIgnoreCase('abcXYZ', ?) ==> "abcXYZ"
Uppercases the first character of a string.
'cat'.capitalize() ==> "Cat"
capitalize('cAt') ==> "CAt"
Centers a String in a larger String of a specified size. Uses a supplied String as the value to pad the String with.
'abc'.center(7) ==> " abc "
'abc'.center(7, 'X') ==> "XXabcXX"
'abc'.center(?, 7, upperCase(?)) ==> "ABabcAB"
center('abc', 7, '') ==> " abc "
4.center('a', ?, 'yz') ==> "yayz"
Concatenates multiple strings together.
'Hello'.concat(2022, '... ', ?, ' World!') ==> "2022... Hello World!"
json('{"a":"Hello","c":" World!"}').concat(a,b,c) ==> !unresolvable!
Concatenates multiple strings together. Ignore null values.
'Hello'.concatFree(2022, '... ', ?, ' World!') ==> "2022... Hello World!"
json('{"a":"Hello","c":" World!"}').concatFree(a,b,c) ==> "Hello World!"
Returns first non-null value from a list of values. Returns an empty string if none found.
json('{"a":1,"b":"B","c":null}').default(x) ==> ""
json('{"a":1,"b":"B","c":null}').default(x,'Hi') ==> "Hi"
json('{"a":1,"b":"B","c":null}').default(x,null,c,a,b) ==> 1
json('{"a":1,"b":"B","c":null}').default(x,null,c,b,a) ==> "B"
Returns substring after a given string.
'abcxmnxyz'.keepAfter('x') ==> "mnxyz"
'abcxmnxyz'.keepAfter(?, 'X') ==> ""
keepAfter('abcxmnxyz', 'mn') ==> "xyz"
Same as above but ignores case.
'abcxmnxyz'.keepAfterIgnoreCase('x') ==> "mnxyz"
'abcxmnxyz'.keepAfterIgnoreCase(?, 'X') ==> "mnxyz"
keepAfterIgnoreCase('abcxmnxyz', 'mn') ==> "xyz"
Returns substring after last occurrence of given string.
'abcxmnxyz'.keepAfterLast('x') ==> "yz"
'abcxmnxyz'.keepAfterLast(?, 'X') ==> ""
keepAfterLast('abcxmnxyz', 'mn') ==> "xyz"
Same as above but ignores case.
'abcxmnxyz'.keepAfterLastIgnoreCase('x') ==> "yz"
'abcxmnxyz'.keepAfterLastIgnoreCase(?, 'X') ==> "yz"
keepAfterLastIgnoreCase('abcxmnxyz', 'mn') ==> "xyz"
Returns substring before a given string.
'abcxmnxyz'.keepBefore('x') ==> "abc"
'abcxmnxyz'.keepBefore(?, 'X') ==> ""
keepBefore('abcxmnxyz', 'mn') ==> "abcx"
Same as above but ignores case.
'abcxmnxyz'.keepBeforeIgnoreCase('x') ==> "abc"
'abcxmnxyz'.keepBeforeIgnoreCase(?, 'X') ==> "abc"
keepBeforeIgnoreCase('abcxmnxyz', 'mn') ==> "abcx"
Returns substring before last occurrence of given string.
'abcxmnxyz'.keepBeforeLast('x') ==> "abcxmn"
'abcxmnxyz'.keepBeforeLast(?, 'X') ==> ""
keepBeforeLast('abcxmnxyz', 'mn') ==> "abcx"
Same as above but ignores case.
'abcxmnxyz'.keepBeforeLastIgnoreCase('x') ==> "abcxmn"
'abcxmnxyz'.keepBeforeLastIgnoreCase(?, 'X') ==> "abcxmn"
keepBeforeLastIgnoreCase('abcxmnxyz', 'mn') ==> "abcx"
Left pad a String with a specified character.
'bat'.leftPad(5) ==> " bat"
'bat'.leftPad(?, 8, 'yz') ==> "yzyzybat"
leftPad('bat', 3, 'yz') ==> "bat"
5.leftPad('bat', ?, '') ==> " bat"
Returns length of a string.
'Josson'.length() ==> 6
length('Josson') ==> 6
length(2022) ==> 4
Converts a String to lower case.
'Cat'.lowerCase() ==> "cat"
lowerCase('cAt') ==> "cat"
Returns the node itself if it is a non-empty string, else returns first evaluated non-empty value from a list.
'abc'.notEmpty('xyz') ==> "abc"
''.notEmpty(null, '', 'xyz') ==> "xyz"
json('{"a":"","b":"","c":"abc"}').notEmpty(a,b,c,'xyz') ==> "abc"
Returns the node itself if it is a non-blank string, else returns first evaluated non-blank value from a list.
'abc'.notBlank('xyz') ==> "abc"
' '.notBlank(null, ' ', 'xyz') ==> "xyz"
json('{"a":" ","b":" ","c":"abc"}').notBlank(a,b,c,'xyz') ==> "abc"
Prepends the prefix to the start of the string.
'abc'.prepend('xyz') ==> "xyzabc"
'abc'.prepend(?, 'xyz') ==> "xyzabc"
prepend('xyzabc', 'xyz') ==> "xyzxyzabc"
'xyz'.prepend('XYZabc', ?) ==> "xyzXYZabc"
Prepends the prefix to the start of the string if the string does not already start with the prefix.
'abc'.prependIfMissing('xyz') ==> "xyzabc"
'abc'.prependIfMissing(?, 'xyz') ==> "xyzabc"
prependIfMissing('xyzabc', 'xyz') ==> "xyzabc"
'xyz'.prependIfMissing('XYZabc', ?) ==> "xyzXYZabc"
Prepends the prefix to the start of the string if the string does not already start, case insensitive, with the prefix.
'abc'.prependIfMissingIgnoreCase('xyz') ==> "xyzabc"
'abc'.prependIfMissingIgnoreCase(?, 'xyz') ==> "xyzabc"
prependIfMissingIgnoreCase('xyzabc', 'xyz') ==> "xyzabc"
'xyz'.prependIfMissingIgnoreCase('XYZabc', ?) ==> "XYZabc"
Removes a substring only if it is at the end of a source string, otherwise returns the source string.
'www.domain.com'.removeEnd('.com') ==> "www.domain"
'www.domain.com'.removeEnd(?, '.Com') ==> "www.domain.com"
removeEnd('www.domain.com', '.com') ==> "www.domain"
Case insensitive removal of a substring if it is at the end of a source string, otherwise returns the source string.
'www.domain.COM'.removeEndIgnoreCase('.com') ==> "www.domain"
'www.domain.com'.removeEndIgnoreCase(?, '.Com') ==> "www.domain"
removeEndIgnoreCase('www.domain.com', '.COM') ==> "www.domain"
Removes a substring only if it is at the beginning of a source string, otherwise returns the source string.
'www.domain.com'.removeStart('www.') ==> "domain.com"
'www.domain.com'.removeStart(?, '.Www') ==> "www.domain.com"
removeStart('www.domain.com', 'www.') ==> "domain.com"
Case insensitive removal of a substring if it is at the beginning of a source string, otherwise returns the source string.
'WWW.domain.com'.removeStartIgnoreCase('www.') ==> "domain.com"
'www.domain.com'.removeStartIgnoreCase(?, '.Www') ==> "www.domain.com"
removeStartIgnoreCase('www.domain.com', 'WWW.') ==> "domain.com"
Repeats a string multiple times to form a new String.
'a'.repeat(3) ==> "aaa"
'ab'.repeat(?, 2) ==> "abab"
repeat('abc', 2) ==> "abcabc"
3.repeat('abc', ?) ==> "abcabcabc"
Replaces a String with another String inside a larger String, for the first specified number (or -1 if no maximum) of values of the search String.
'abaa'.replace('a', 'z') ==> "zbzz"
'abaa'.replace(?, 'a', 'z', -1) ==> "zbzz"
replace('abaa', 'a', '', -1) ==> "b"
replace('abaa', 'A', 'z', 1) ==> "abaa"
'a'.replace('abaa', ?, 'z', 2) ==> "zbza"
Same as above but ignores case.
'abaa'.replaceIgnoreCase('a', 'z') ==> "zbzz"
'abaa'.replaceIgnoreCase(?, 'a', 'z', -1) ==> "zbzz"
replaceIgnoreCase('abaa', 'a', '', -1) ==> "b"
replaceIgnoreCase('abaa', 'A', 'z', 1) ==> "zbaa"
'a'.replaceIgnoreCase('abaa', ?, 'z', 2) ==> "zbza"
Right pad a String with a specified character.
'bat'.rightPad(5) ==> "bat "
'bat'.rightPad(?, 8, 'yz') ==> "batyzyzy"
rightPad('bat', 3, 'yz') ==> "bat"
rightPad('bat', 5, '') ==> "bat "
Splits the provided text into an array, separators specified.
'abc def'.split() ==> [ "abc", "def" ]
'abc def'.split(' ') ==> [ "abc", "def" ]
' abc def '.split(?, ' ') ==> [ "abc", "def" ]
split('ab:cd:ef', ':') ==> [ "ab", "cd", "ef" ]
Splits keeping at most N substrings. A zero or negative value implies no limit.
'ab:cd:ef'.splitMax(':', 0) ==> [ "ab", "cd", "ef" ]
splitMax('ab:cd:ef', ':', 2, true) ==> [ "ab", "cd:ef" ]
Splits the provided text into an array, separators specified.
'ab:cd:ef'.separate(':') ==> [ "ab", "cd", "ef" ]
separate('ab-!-cd-!-ef', '-!-') ==> [ "ab", "cd", "ef" ]
Splits keeping at most N substrings. A zero or negative value implies no limit.
'ab:cd:ef'.separateMax(':', 0) ==> [ "ab", "cd", "ef" ]
separateMax('ab-!-cd-!-ef', '-!-', 2, true) ==> [ "ab", "cd-!-ef" ]
Strips whitespace from the start and end of a String.
' abc '.strip(' ') ==> "abc"
' abcyx'.strip('xyz') ==> " abc"
strip('z abcyx', 'xyz') ==> " abc"
Strips any of a set of characters from the end of a String.
' abc '.stripEnd(' ') ==> " abc"
'z abcyx'.stripEnd('xyz') ==> "z abc"
stripEnd('z abcyx', 'xyz') ==> "z abc"
Strips any of a set of characters from the start of a String.
' abc '.stripStart(' ') ==> "abc "
'z abcyx'.stripStart('xyz') ==> " abcyx"
stripStart('z abcyx', 'xyz') ==> " abcyx"
Gets a substring from the specified String avoiding exceptions.
A negative start position can be used to start/end N characters from the end of the String.
'abc'.substr(1) ==> "bc"
'abc'.substr(0, 2) ==> "ab"
'abc'.substr(?, 1, 2) ==> "b"
substr('abc', -2, -1) ==> "b"
2.substr('abc', -4, ?) ==> "ab"
Removes control characters (char <= 32) from both ends of this String.
'abc'.trim() ==> "abc"
trim(' abc ') ==> "abc"
Uncapitalizes a String, changing the first character to lower case.
'Cat'.uncapitalize() ==> "cat"
uncapitalize('CAt') ==> "cAt"
Converts a String to upper case.
'Cat'.upperCase() ==> "CAT"
upperCase('cAt') ==> "CAT"
Converts words in a String to camel case, with the first word in lower case.
'i am a man .and..i have_a__pen'.camelCase() ==> "iAmAManAndIHaveAPen"
' Text to__c@mel case '.camelCase() ==> "textToC@melCase"
' Text to__c@mel case '.camelCase(' _.@') ==> "textToCMelCase"
Converts words in a String to camel case, with the first word also in camel case.
'i am a man .and..i have_a__pen'.camelCase() ==> "IAmAManAndIHaveAPen"
' Text to__c@mel case '.camelCase() ==> "TextToC@melCase"
' Text to__c@mel case '.camelCase(' _.@') ==> "TextToCMelCase"
Converts words in a String to snake case.
' Text to__snake case '.snakeCase() ==> "Text_to_snake_case"
'camelToSnakeCase'.snakeCase() ==> "camel_To_Snake_Case"
Converts words in a String to lower snake case.
' Text to__snake case '.lowerSnakeCase() ==> "text_to_snake_case"
'camelToSnakeCase'.lowerSnakeCase() ==> "camel_to_snake_case"
Converts words in a String to upper snake case.
' Text to__snake case '.upperSnakeCase() ==> "TEXT_TO_SNAKE_CASE"
'camelToSnakeCase'.upperSnakeCase() ==> "CAMEL_TO_SNAKE_CASE"
Converts words in a String to camel snake case.
' Text to__snake case '.camelSnakeCase() ==> "Text_To_Snake_Case"
'camelToSnakeCase'.camelSnakeCase() ==> "Camel_To_Snake_Case"
Adds surrounding quotes with escape handling.
'Peggy''s cat'.singleQuote() ==> "'Peggy''s cat'"
123.singleQuote() ==> "'123'"
quote('Raymond''s dog') ==> "'Raymond''s dog'"
q(True) ==> "'true'"
Adds surrounding double quotes with escape handling.
'Peggy\"s cat'.doubleQuote() ==> "\"Peggy\\\"s cat\""
12.3.doubleQuote() ==> "\"12.3\""
qq('Raymond\"s dog') ==> "\"Raymond\\\"s dog\""
qq(False) ==> "\"false\""
Returns the AM/PM indicator of a datetime value in uppercase.
'2022-01-02T03:04:05'.amPmOfDay() ==> "AM"
amPmOfDay('2022-02-04T13:14:15') ==> "PM"
Returns the second component (from 0 to 60) of a datetime.
'2022-01-02T03:04:05'.second() ==> 5
second('2022-02-04T13:14:15') ==> 15
Returns the seconds since midnight (from 0 to 86399) of a datetime.
'2022-01-02T03:04:05'.secondOfDay() ==> 11045
secondOfDay('2022-02-04T13:14:15') ==> 47655
Returns the minute component (from 0 to 59) of a datetime.
'2022-01-02T03:04:05'.minute() ==> 4
minute('2022-02-04T13:14:15') ==> 14
Returns the minutes since midnight (from 0 to 1439) of a datetime.
'2022-01-02T03:04:05'.minuteOfDay() ==> 184
minuteOfDay('2022-02-04T13:14:15') ==> 794
Returns the hour (from 1 to 12) component of a datetime adjusted for AM/PM.
'2022-01-02T03:04:05'.hourOfAmPm() ==> 3
hourOfAmPm('2022-02-04T13:14:15') ==> 1
Returns the hour (from 0 to 23) component of a datetime.
'2022-01-02T03:04:05'.hour() ==> 3
hour('2022-02-04T13:14:15') ==> 13
Returns the day of week as number (from 1 to 7) with Monday=1.
'2022-01-02T03:04:05'.dayOfWeek() ==> 7
dayOfWeek('2022-02-04T13:14:15') ==> 5
Returns the day of month (from 1 to 31) component of a datetime.
'2022-01-02T03:04:05'.day() ==> 2
day('2022-02-04T13:14:15') ==> 4
Returns the day number of the year (from 1 to 366) of a datetime.
'2022-01-02T03:04:05'.dayOfYear() ==> 2
dayOfYear('2022-02-04T13:14:15') ==> 35
Returns the month (from 1 to 12) component of a datetime.
'2022-01-02T03:04:05'.month() ==> 1
month('2022-02-04T13:14:15') ==> 2
Returns the year component of a datetime.
'2022-01-02T03:04:05'.year() ==> 2022
year('2022-02-04T13:14:15') ==> 2022
Adds the specified seconds to a datetime value.
'2022-01-02T03:04:05'.plusSeconds(9) ==> "2022-01-02T03:04:14"
'2022-01-02T03:04:05'.plusSeconds(?, 10) ==> "2022-01-02T03:04:15"
plusSeconds('2022-02-04T13:14:15', 9) ==> "2022-02-04T13:14:24"
Adds the specified minutes to a datetime value.
'2022-01-02T03:04:05'.plusMinutes(9) ==> "2022-01-02T03:13:05"
'2022-01-02T03:04:05'.plusMinutes(?, 10) ==> "2022-01-02T03:14:05"
plusMinutes('2022-02-04T13:14:15', 9) ==> "2022-02-04T13:23:15"
Adds the specified hours to a datetime value.
'2022-01-02T03:04:05'.plusHours(9) ==> "2022-01-02T12:04:05"
'2022-01-02T03:04:05'.plusHours(?, 10) ==> "2022-01-02T13:04:05"
plusHours('2022-02-04T13:14:15', 9) ==> "2022-02-04T22:14:15"
Adds the specified days to a datetime value.
'2022-01-02T03:04:05'.plusDays(9) ==> "2022-01-11T03:04:05"
'2022-01-02T03:04:05'.plusDays(?, 10) ==> "2022-01-12T03:04:05"
plusDays('2022-02-04T13:14:15', 9) ==> "2022-02-13T13:14:15"
Adds the specified weeks to a datetime value.
'2022-01-02T03:04:05'.plusWeeks(9) ==> "2022-03-06T03:04:05"
'2022-01-02T03:04:05'.plusWeeks(?, 10) ==> "2022-03-13T03:04:05"
plusWeeks('2022-02-04T13:14:15', 9) ==> "2022-04-08T13:14:15"
Adds the specified months to a datetime value.
'2022-01-02T03:04:05'.plusMonths(9) ==> "2022-10-02T03:04:05"
'2022-01-02T03:04:05'.plusMonths(?, 10) ==> "2022-11-02T03:04:05"
plusMonths('2022-02-04T13:14:15', 9) ==> "2022-11-04T13:14:15"
Adds the specified years to a datetime value.
'2022-01-02T03:04:05'.plusYears(9) ==> "2031-01-02T03:04:05"
'2022-01-02T03:04:05'.plusYears(?, 10) ==> "2032-01-02T03:04:05"
plusYears('2022-02-04T13:14:15', 9) ==> "2031-02-04T13:14:15"
Subtracts the specified seconds from a datetime value.
'2022-01-02T03:04:05'.minusSeconds(9) ==> "2022-01-02T03:03:56"
'2022-01-02T03:04:05'.minusSeconds(?, 10) ==> "2022-01-02T03:03:55"
minusSeconds('2022-02-04T13:14:15', 9) ==> "2022-02-04T13:14:06"
Subtracts the specified minutes from a datetime value.
'2022-01-02T03:04:05'.minusMinutes(9) ==> "2022-01-02T02:55:05"
'2022-01-02T03:04:05'.minusMinutes(?, 10) ==> "2022-01-02T02:54:05"
minusMinutes('2022-02-04T13:14:15', 9) ==> "2022-02-04T13:05:15"
Subtracts the specified hours from a datetime value.
'2022-01-02T03:04:05'.minusHours(9) ==> "2022-01-01T18:04:05"
'2022-01-02T03:04:05'.minusHours(?, 10) ==> "2022-01-01T17:04:05"
minusHours('2022-02-04T13:14:15', 9) ==> "2022-02-04T04:14:15"
Subtracts the specified days from a datetime value.
'2022-01-02T03:04:05'.minusDays(9) ==> "2021-12-24T03:04:05"
'2022-01-02T03:04:05'.minusDays(?, 10) ==> "2021-12-23T03:04:05"
minusDays('2022-02-04T13:14:15', 9) ==> "2022-01-26T13:14:15"
Subtracts the specified weeks from a datetime value.
'2022-01-02T03:04:05'.minusWeeks(9) ==> "2021-10-31T03:04:05"
'2022-01-02T03:04:05'.minusWeeks(?, 10) ==> "2021-10-24T03:04:05"
minusWeeks('2022-02-04T13:14:15', 9) ==> "2021-12-03T13:14:15"
Subtracts the specified months from a datetime value.
'2022-01-02T03:04:05'.minusMonths(9) ==> "2021-04-02T03:04:05"
'2022-01-02T03:04:05'.minusMonths(?, 10) ==> "2021-03-02T03:04:05"
minusMonths('2022-02-04T13:14:15', 9) ==> "2021-05-04T13:14:15"
Subtracts the specified years from a datetime value.
'2022-01-02T03:04:05'.minusYears(9) ==> "2013-01-02T03:04:05"
'2022-01-02T03:04:05'.minusYears(?, 10) ==> "2012-01-02T03:04:05"
minusYears('2022-02-04T13:14:15', 9) ==> "2013-02-04T13:14:15"
Truncates a datetime to a less precise value.
'2022-01-02T03:04:05.229390600'.truncateToMicro() ==> "2022-01-02T03:04:05.229390"
truncateToMicro('2022-02-04T13:14:15.229390600') ==> "2022-02-04T13:14:15.229390"
Truncates a datetime to a less precise value.
'2022-01-02T03:04:05.229390600'.truncateToMilli() ==> "2022-01-02T03:04:05.229"
truncateToMilli('2022-02-04T13:14:15.229390600') ==> "2022-02-04T13:14:15.229"
Truncates a datetime to a less precise value.
'2022-01-02T03:04:05.229390600'.truncateToSecond() ==> "2022-01-02T03:04:05"
truncateToSecond('2022-02-04T13:14:15.229390600') ==> "2022-02-04T13:14:15"
Truncates a datetime to a less precise value.
'2022-01-02T03:04:05.229390600'.truncateToMinute() ==> "2022-01-02T03:04"
truncateToMinute('2022-02-04T13:14:15.229390600') ==> "2022-02-04T13:14"
Truncates a datetime to a less precise value.
'2022-01-02T03:04:05.229390600'.truncateToHour() ==> "2022-01-02T03:00"
truncateToHour('2022-02-04T13:14:15.229390600') ==> "2022-02-04T13:00"
Truncates a datetime to a less precise value.
'2022-01-02T03:04:05.229390600'.truncateToDay() ==> "2022-01-02T00:00"
truncateToDay('2022-02-04T13:14:15.229390600') ==> "2022-02-04T00:00"
Truncates a datetime to a less precise value.
'2022-01-02T03:04:05.229390600'.truncateToMonth() ==> "2022-01-01T00:00"
truncateToMonth('2022-02-04T13:14:15.229390600') ==> "2022-02-01T00:00"
Truncates a datetime to a less precise value.
'2022-01-02T03:04:05.229390600'.truncateToYear() ==> "2022-01-01T00:00"
truncateToYear('2022-02-04T13:14:15.229390600') ==> "2022-01-01T00:00"
Sets a component of a datetime value.
'2022-01-02T03:04'.withNano(789) ==> "2022-01-02T03:04:00.000000789"
'2022-01-02T03:04'.withNano(?, 789) ==> "2022-01-02T03:04:00.000000789"
withNano('2022-02-04T13:14', 789) ==> "2022-02-04T13:14:00.000000789"
Sets a component of a datetime value.
'2022-01-02T03:04'.withMicro(789) ==> "2022-01-02T03:04:00.000789"
'2022-01-02T03:04'.withMicro(?, 789) ==> "2022-01-02T03:04:00.000789"
withMicro('2022-02-04T13:14', 789) ==> "2022-02-04T13:14:00.000789"
Sets a component of a datetime value.
'2022-01-02T03:04'.withMilli(789) ==> "2022-01-02T03:04:00.789"
'2022-01-02T03:04'.withMilli(?, 789) ==> "2022-01-02T03:04:00.789"
withMilli('2022-02-04T13:14', 789) ==> "2022-02-04T13:14:00.789"
Sets a component of a datetime value.
'2022-01-02T03:04'.withSecond(35) ==> "2022-01-02T03:04:35"
'2022-01-02T03:04'.withSecond(?, 35) ==> "2022-01-02T03:04:35"
withSecond('2022-02-04T13:14', 35) ==> "2022-02-04T13:14:35"
Sets a component of a datetime value.
'2022-01-02T03:04'.withMinute(35) ==> "2022-01-02T03:35"
'2022-01-02T03:04'.withMinute(?, 35) ==> "2022-01-02T03:35"
withMinute('2022-02-04T13:14', 35) ==> "2022-02-04T13:35"
Sets a component of a datetime value.
'2022-01-02T03:04'.withHour(16) ==> "2022-01-02T16:04"
'2022-01-02T03:04'.withHour(?, 16) ==> "2022-01-02T16:04"
withHour('2022-02-04T13:14', 16) ==> "2022-02-04T16:14"
Sets a component of a datetime value.
'2022-01-02T03:04'.withDay(25) ==> "2022-01-25T03:04"
'2022-01-02T03:04'.withDay(?, 25) ==> "2022-01-25T03:04"
withDay('2022-02-04T13:14', 25) ==> "2022-02-25T13:14"
Sets a component of a datetime value.
'2022-01-02T03:04'.withDayOfYear(123) ==> "2022-05-03T03:04"
'2022-01-02T03:04'.withDayOfYear(?, 123) ==> "2022-05-03T03:04"
withDayOfYear('2022-02-04T13:14', 123) ==> "2022-05-03T13:14"
Sets a component of a datetime value.
'2022-01-02T03:04'.withMonth(7) ==> "2022-07-02T03:04"
'2022-01-02T03:04'.withMonth(?, 7) ==> "2022-07-02T03:04"
withMonth('2022-02-04T13:14', 7) ==> "2022-07-04T13:14"
Sets a component of a datetime value.
'2022-01-02T03:04'.withYear(2047) ==> "2047-01-02T03:04"
'2022-01-02T03:04'.withYear(?, 2047) ==> "2047-01-02T03:04"
withYear('2022-02-04T13:14', 2047) ==> "2047-02-04T13:14"
Returns the last millisecond of a day for a datetime.
'2022-01-02T03:04'.dayEnd() ==> "2022-01-02T23:59:59.999999999"
dayEnd('2022-02-04T13:14') ==> "2022-02-04T23:59:59.999999999"
Returns the last millisecond of a month for a datetime.
'2022-01-02T03:04'.monthEnd() ==> "2022-01-31T23:59:59.999999999"
monthEnd('2022-02-04T13:14') ==> "2022-02-28T23:59:59.999999999"
Returns the last millisecond of a year for a datetime.
'2022-01-02T03:04'.yearEnd() ==> "2022-12-31T23:59:59.999999999"
yearEnd('2022-02-04T13:14') ==> "2022-12-31T23:59:59.999999999"
Returns number of days in the month of a datetime.
'2022-01-02T03:04'.lengthOfMonth() ==> 31
lengthOfMonth('2022-02-04T13:14') ==> 28
Returns 366 if a datetime is a leap year, else 365.
'2022-01-02T03:04'.lengthOfYear() ==> 365
lengthOfYear('2024-02-04T13:14') ==> 366
Calculates the difference between two datetimes in seconds.
'2020-01-02T23:04'.untilInSecond('2022-06-11T01:02') ==> 76903080
untilInSecond('2021-12-12T13:14','2021-03-03T01:00') ==> -24581640
Calculates the difference between two datetimes in minutes.
'2020-01-02T23:04'.untilInMinute('2022-06-11T01:02') ==> 1281718
untilInMinute('2021-12-12T13:14','2021-03-03T01:00') ==> -409694
Calculates the difference between two datetimes in hours.
'2020-01-02T23:04'.untilInHour('2022-06-11T01:02') ==> 21361
untilInHour('2021-12-12T13:14','2021-03-03T01:00') ==> -6828
Calculates the difference between two datetimes in days.
'2020-01-02T23:04'.untilInDay('2022-06-11T01:02') ==> 890
untilInDay('2021-12-12T13:14','2021-03-03T01:00') ==> -284
Calculates the difference between two datetimes in months.
'2020-01-02T23:04'.untilInMonth('2022-06-11T01:02') ==> 29
untilInMonth('2021-12-12T13:14','2021-03-03T01:00') ==> -9
Calculates the difference between two datetimes in years.
'2020-01-02T23:04'.untilInYear('2022-06-11T01:02') ==> 2
untilInYear('2021-12-12T13:14','2021-03-03T01:00') ==> 0
Converts local datetime to offset datetime format.
'2022-01-02T03:04:05'.localToOffsetDate() ==> "2022-01-02T03:04:05+08:00"
localToOffsetDate('2022-02-04T13:14:15') ==> "2022-02-04T13:14:15+08:00"
Converts offset datetime to local datetime format.
'2022-01-02T03:04:05+08:00'.offsetToLocalDate() ==> "2022-01-02T03:04:05"
offsetToLocalDate('2022-02-04T13:14:15+08:00') ==> "2022-02-04T13:14:15"
Converts local datetime format to milliseconds.
'2022-01-02T03:04:05.12345'.localDateToMillis() ==> 1641063845123
localDateToMillis('2022-02-04T13:14:15.12345') ==> 1643951655123
Converts milliseconds to local datetime format.
1641063845123.millisToLocalDate() ==> "2022-01-02T03:04:05.123"
millisToLocalDate(1643951655123) ==> "2022-02-04T13:14:15.123"
Converts local datetime format to seconds.
'2022-01-02T03:04:05.12345'.localDateToSeconds() ==> 1641063845
localDateToSeconds('2022-02-04T13:14:15.12345') ==> 1643951655
Converts seconds to local datetime format.
1641063845.secondsToLocalDate() ==> "2022-01-02T03:04:05"
secondsToLocalDate(1643951655) ==> "2022-02-04T13:14:15"
Converts offset datetime format to milliseconds.
'2022-01-02T03:04:05.12345+08:00'.offsetDateToMillis() ==> 1641063845123
offsetDateToMillis('2022-02-04T13:14:15.12345+08:00') ==> 1643951655123
Converts milliseconds to offset datetime format.
1641063845123.millisToOffsetDate() ==> "2022-01-02T03:04:05.123+08:00"
millisToOffsetDate(1643951655123) ==> "2022-02-04T13:14:15.123+08:00"
Converts offset datetime format to seconds.
'2022-01-02T03:04:05.12345+08:00'.offsetDateToSeconds() ==> 1641063845
offsetDateToSeconds('2022-02-04T13:14:15.12345+08:00') ==> 1643951655
Converts seconds to offset datetime format.
1641063845.secondsToOffsetDate() ==> "2022-01-02T03:04:05+08:00"
secondsToOffsetDate(1643951655) ==> "2022-02-04T13:14:15+08:00"
Returns the current date and time in local datetime format.
now() ==> "2022-10-30T23:52:47.084650900"
now().localToOffsetDate() ==> 2022-10-31T01:02:27.294741100+08:00s
Encodes a string to base64 format.
'abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ'.b64Encode()
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=="
Encodes without padding at the end.
b64EncodeNoPadding('abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ')
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg"
Encodes for MIME format with newlines every 76 chars.
'abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ'.b64MimeEncode()
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\r\nUVJTVFVWV1hZWg=="
Encodes for MIME without padding.
b64MimeEncodeNoPadding('abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ')
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\r\nUVJTVFVWV1hZWg"
Encodes for URL format by replacing padding chars.
'abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ'.b64UrlEncode()
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=="
Encodes for URL without padding.
b64UrlEncodeNoPadding('abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ')
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg"
Decodes a base64 encoded string.
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=='.b64Decode()
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
b64Decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg')
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Decodes a base64 encoded string from MIME format.
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\nUVJTVFVWV1hZWg=='.b64MimeDecode()
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
b64MimeDecode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\r\nUVJTVFVWV1hZWg')
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Decodes from URL safe format.
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=='.b64UrlDecode()
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
b64UrlDecode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg')
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
URL encodes a string.
'www.domain.com?a=1+2&b=3+4'.urlEncode() ==> "www.domain.com%3Fa%3D1%2B2%26b%3D3%2B4"
urlEncode('www.domain.com?a=1+2&b=3+4') ==> "www.domain.com%3Fa%3D1%2B2%26b%3D3%2B4"
Decodes a URL encoded string.
'www.domain.com%3Fa%3D1%2B2%26b%3D3%2B4'.urlDecode() ==> "www.domain.com?a=1+2&b=3+4"
urlDecode('www.domain.com%3Fa%3D1%2B2%26b%3D3%2B4') ==> "www.domain.com?a=1+2&b=3+4"
Translator object for escaping HTML version 4.0.
'~!@#$%^&*()<>[]{}+-= "''\|_:;,./?'.escapeHtml() ==> "~!@#$%^&*()<>[]{}+-= "'\|_:;,./?"
Reverses HTML escaping.
'~!@#$%^&*()<>[]{}+-= "''\|_:;,./?'.unescapeHtml() ==> "~!@#$%^&*()<>[]{}+-= "'\|_:;,./?"
Translator object for escaping XML 1.1.
'~!@#$%^&*()<>[]{}+-= "''\|_:;,./?'.escapeXml() ==> "~!@#$%^&*()<>[]{}+-= "'\|_:;,./?"
Reverses XML escaping.
'~!@#$%^&*()<>[]{}+-= "'\|_:;,./?'.unescapeXml() ==> "~!@#$%^&*()<>[]{}+-= "'\|_:;,./?"
Formats a local datetime to a string.
'2022-01-02T03:04:05'.formatDate('dd/MM/yyyy HH:mm:ss') ==> "02/01/2022 03:04:05"
'2022-01-02T03:04:05'.formatDate(?, 'yyyy-MM-dd') ==> "2022-01-02"
formatDate('2022-01-02T03:04:05', 'EEE, MMM d, yyyy') ==> "Sun, Jan 2, 2022"
Formats a number to string using a format pattern.
12345.6.formatNumber('HK$#,##0.00') ==> "HK$12,345.60"
123.formatNumber(?, '#,##0.#') ==> "123"
formatNumber(123.45, '#,##0.#') ==> "123.5"
Formats a text using a format pattern.
'Dog'.formatText('[%-5s]') ==> "[Dog ]"
123.formatText(?, '[%5d]') ==> "[ 123]"
formatText('Dog', '[%5s]') ==> "[ Dog]"
Formats multiple texts using a format pattern.
formatTexts('1:%s 2:%s 3:%s', 'a', 'b', 'c') ==> "1:a 2:b 3:c"
'b'.formatTexts('1:%s 2:%s 3:%s', 'a', ?, 'c') ==> "1:a 2:b 3:c"
json('{"A":"a","B":"b"}').formatTexts('1:%s 2:%s 3:%s', A, B, 'c') ==> "1:a 2:b 3:c"
json('[{"a":1,"b":3},{"a":2,"b":4}]').formatTexts('a=%d b=%d',a,b) ==> [ "a=1 b=3", "a=2 b=4" ]
Converts supported types to number.
'123'.toNumber() ==> 123.0
toNumber('abc') ==> 0.0
toNumber(true) ==> 1.0
toNumber(null) ==> !unresolvable!
toNumber(json('{"a":1}')) ==> !unresolvable!
toNumber(json('[1,2.0,"a",true,null]')) ==> [ 1, 2.0, 0.0, 1.0, null ]
Converts supported types, including object and array, to a string.
123.toString() ==> "123"
toString(false) ==> "false"
toString(null) ==> "null"
toString(json('{"a":1}')) ==> "{\"a\":1}"
toString(json('[1,2.0,"a",true,null]')) ==> "[1,2.0,\"a\",true,null]"
Converts supported types to string.
123.toText() ==> "123"
toText(false) ==> "false"
toText(null) ==> "null"
toText(json('{"a":1}')) ==> !unresolvable!
toText(json('[1,2.0,"a",true,null]')) ==> [ "1", "2.0", "a", "true", "null" ]
Encodes arrays or objects to a CSV string. Ignores null values.
json('{"len1":"12.3\"","len2":null,"len3":"64.0\""}').csv() ==> "\"12.3\"\"\",,\"64.0\"\"\""
csv(json('[[[[1,2],["3","4\""]]],{"a":1,"b":[2.0,8.888],"c":{"d":true,"e":null}}]')) ==> "1,2,3,\"4\"\"\",1,2.0,8.888,true,"
Encodes arrays or objects to a CSV string. Includes null values.
json('{"len1":"12.3\"","len2":null,"len3":"64.0\""}').csvShowNull() ==> "12.3\"\"\",null,\"64.0\"\""
csvShowNull(json('[[[[1,2],["3","4\""]]],{"a":1,"b":[2.0,8.888],"c":{"d":true,"e":null}}]')) ==> "1,2,3,\"4\"\"\",1,2.0,8.888,true,null"
Encodes arrays or objects to a single quoted CSV string. Includes null values.
It is useful to construct function parameters from object or array values for Jossons nested placeholders feature.
json('{"len1":"12.3","len2":null,"len3":"64.0\""}').csvParams() ==> "'12.3',null,'64.0\"'"
csvParams(json('[[[[1,2],["3","4''"]]],{"a":1,"b":[2.0,8.888],"c":{"d":true,"e":null}}]')) ==> "1,2,'3','4''',1,2.0,8.888,true,null"
Checks if a string contains another string.
'abcde'.contains('bc') ==> true
contains('abcde','B') ==> false
json('[1.0,2.8,3.0]').contains(?, '1') ==> false
json('[1.0,2.8,3.0]').contains(1) ==> true
contains(json('["1","2","3"]'), 2.0) ==> true
contains(json('[1.0,2.8,3.00]'), '3.0') ==> true
json('["1.0","2.0","3.0"]').contains(?, '3.0') ==> true
json('[1,2,null,4]').contains(null) ==> true
json('{"a":1,"b":2,"c":3}').contains('a') ==> true
Same as above but ignores case sensitivity.
'abcde'.containsIgnoreCase('bc') ==> true
containsIgnoreCase('abcde','B') ==> true
json('["a","b","c"]').containsIgnoreCase(?, 'B') ==> true
containsIgnoreCase(json('["a","b","c"]'), 'bc') ==> false
json('{"a":1,"b":2,"c":3}').containsIgnoreCase('A') ==> true
Checks if a string does not contain another string.
'abcde'.notContains('bc') ==> false
notContains('abcde','B') ==> true
json('[1.0,2.8,3.0]').notContains(?, 1) ==> false
json('[1,2,null,4]').notContains(null) ==> false
Same as above but ignores case sensitivity.
'abcde'.notContainsIgnoreCase('bc') ==> false
notContainsIgnoreCase('abcde','B') ==> false
json('["a","b","c"]').notContainsIgnoreCase(?, 'D') ==> true
Checks if a string starts with a prefix.
'abcdef'.startsWith('abc') ==> true
'ABCDEF'.startsWith(?,'abc') ==> false
startsWith('ABCDEF','cde') ==> false
Same as above but ignores case sensitivity.
'abcdef'.startsWithIgnoreCase('abc') ==> true
'ABCDEF'.startsWithIgnoreCase(?,'abc') ==> true
startsWithIgnoreCase('ABCDEF','cde') ==> false
Checks if a string does not start with a prefix.
'abcdef'.notStartsWith('abc') ==> false
'ABCDEF'.notStartsWith(?,'abc') ==> true
notStartsWith('ABCDEF','cde') ==> true
Same as above but ignores case sensitivity.
'abcdef'.notStartsWithIgnoreCase('abc') ==> false
'ABCDEF'.notStartsWithIgnoreCase(?,'abc') ==> false
notStartsWithIgnoreCase('ABCDEF','cde') ==> true
Checks if a string ends with a suffix.
'abcdef'.endsWith('def') ==> true
'ABCDEF'.endsWith(?,'def') ==> false
endsWith('ABCDEF','cde') ==> false
Same as above but ignores case sensitivity.
'abcdef'.endsWithIgnoreCase('def') ==> true
'ABCDEF'.endsWithIgnoreCase(?,'def') ==> true
endsWithIgnoreCase('ABCDEF','cde') ==> false
Checks if a string does not end with a suffix.
'abcdef'.notEndsWith('def') ==> false
'ABCDEF'.notEndsWith(?,'def') ==> true
notEndsWith('ABCDEF','cde') ==> true
Same as above but ignores case sensitivity.
'abcdef'.notEndsWithIgnoreCase('def') ==> false
'ABCDEF'.notEndsWithIgnoreCase(?,'def') ==> false
notEndsWithIgnoreCase('ABCDEF','cde') ==> true
Checks if two values are equal.
'abc'.equals('abc') ==> true
'abc'.equals(?,' abc') ==> false
equals('ABC','abc') ==> false
Same as above but ignores case sensitivity.
'abc'.equalsIgnoreCase('abc') ==> true
'abc'.equalsIgnoreCase(?,' abc') ==> false
equalsIgnoreCase('ABC','abc') ==> true
Checks if two values are not equal.
'abc'.notEquals('abc') ==> false
'abc'.notEquals(?, ' abc') ==> true
notEquals('ABC','abc') ==> true
Same as above but ignores case sensitivity.
'abc'.notEqualsIgnoreCase('abcd') ==> true
'abc'.notEqualsIgnoreCase(' abc') ==> true
notEqualsIgnoreCase('ABC','abc') ==> false
Checks if a string matches a regular expression.
'123a'.matches('^[0-9]+$') ==> false
'784238'.matches(?,'^[0-9]+$') ==> true
matches('63 56','^[0-9]+$') ==> false
Checks if a string does not match a regex.
'1234-123456'.notMatches('\\d{4}-\\d{6}') ==> false
'888-123456'.notMatches(?,'\\d{4}-\\d{6}') ==> true
notMatches('4444-5555','\\d{4}-\\d{6}') ==> true
Checks if a value exists in an array.
56.in(12,34,56) ==> true
'56'.in(12,34,56) ==> true
'A'.in(json('["a","b","c"]')) ==> false
Same as above but ignores case sensitivity.
'A'.inIgnoreCase('a','b','c') ==> true
'a '.inIgnoreCase('a','b','c') ==> false
Checks if a value does not exist in an array.
56.notIn(12,34,56) ==> false
'56'.notIn(12,34,56) ==> false
'A'.notIn(json('["a","b","c"]')) ==> true
Same as above but ignores case sensitivity.
'A'.notInIgnoreCase('a','b','c') ==> false
'a '.notInIgnoreCase('a','b','c') ==> true
Checks if a value is empty.
''.isEmpty() ==> true
isEmpty(' ') ==> false
isEmpty(1) ==> false
isEmpty(true) ==> false
isEmpty(null) ==> true
isEmpty(json('[""," ",0,false,null]')) ==> [ true, false, false, false, true ]
Checks if a value is not empty.
''.isNotEmpty() ==> false
isNotEmpty(' ') ==> true
isNotEmpty(1) ==> true
isNotEmpty(true) ==> true
isNotEmpty(null) ==> false
isNotEmpty(json('[""," ",0,false,null]')) ==> [ false, true, true, true, false ]
Checks if a value is whitespace, empty or null.
''.isBlank() ==> true
isBlank(' ') ==> true
isBlank(json('[""," ","X",0,false,null]')) ==> [ true, true, false, false, false, false ]
Checks if a value is not blank.
''.isNotBlank() ==> false
isNotBlank(' ') ==> false
isNotBlank(json('[""," ","X",0,false,null]')) ==> [ false, false, true, false, false, false ]
Checks if a value is null.
null.isNull() ==> true
isNull(null) ==> true
isNull('') ==> false
isNull(json('["text",1,true,null]')) ==> [ false, false, false, true ]
Checks if a value is not null.
null.isNotNull() ==> false
isNotNull(null) ==> false
isNotNull('') ==> true
isNotNull(json('["text",1,true,null]')) ==> [ true, true, true, false ]
Checks if a value is a text type.
'text'.isText() ==> true
isText(1) ==> false
isText(true) ==> false
isText(json('["text",1,true,null]')) ==> [ true, false, false, false ]
Checks if a value is a boolean.
'text'.isBoolean() ==> false
isBoolean(1) ==> false
isBoolean(true) ==> true
isBoolean(json('["text",1,true,null]')) ==> [ false, false, true, false ]
Checks if a value is a number.
'text'.isNumber() ==> false
isNumber(1) ==> true
isNumber(true) ==> false
isNumber(json('["text",1,true,null]')) ==> [ false, true, false, false ]
Checks if a number is even.
1.isEven() ==> false
isEven(2) ==> true
isEven(json('["text",1,2,null]')) ==> [ false, false, true, false ]
Checks if a number is odd.
1.isOdd() ==> true
isOdd(2) ==> false
isOdd(json('["text",1,2,null]')) ==> [ false, true, false, false ]
Checks if a value is an array.
'text'.isArray() ==> false
isArray(1) ==> false
isArray(null) ==> false
json('[1,2]').isArray() ==> true
isArray(json('{"a":1}')) ==> false
Checks if a value is an object.
'text'.isObject() ==> false
isObject(1) ==> false
isObject(null) ==> false
json('[1,2]').isObject() ==> false
isObject(json('{"a":1}')) ==> true
Checks if an array is empty.
json('[]').isEmptyArray() ==> true
isEmptyArray(json('[0]')) ==> false
Checks if an object is empty.
json('{}').isEmptyObject() ==> true
isEmptyObject(json('{"a":1}')) ==> false
Inverse a boolean value.
true.not() ==> false
not(false) ==> true
not('false') ==> false
not(0) ==> false
not(null) ==> false
Checks if a local datetime is a weekday.
'2021-12-31T00:00:00'.isWeekday() ==> true
isWeekday('2022-01-01T00:00:00') ==> false
Checks if a local datetime is a Saturday or Sunday.
'2021-12-31T00:00:00'.isWeekend() ==> false
isWeekend('2022-01-01T00:00:00') ==> true
Checks if a local datetime is a leap year.
'2020-12-31T00:00:00'.isLeapYear() ==> true
isLeapYear('2022-01-01T00:00:00') ==> false
Returns the number of elements, including null value, in an array.
json('[7,1,9,null,5,3]').size() ==> 6
size(json('[7,1,9,null,5,3]')) ==> 6
Returns the index of the last element in an array.
json('[7,1,9,null,5,3]').lastIndex() ==> 5
lastIndex(json('[7,1,9,null,5,3]')) ==> 5
Returns the first index of a specific element in an array.
json('[1,1,3,5,null,3,7,3,9]').indexOf(3) ==> 2
json('[1,1,3,5,null,3,7,3,9]').indexOf(?, '1') ==> 0
indexOf(json('[1,1,3,5,null,3,7,3,9]'), null) ==> 4
Returns the last index of a specific element in an array.
json('[1,1,3,5,null,3,7,3,9]').lastIndexOf(3) ==> 7
json('[1,1,3,5,null,3,7,3,9]').lastIndexOf(?, '1') ==> 1
lastIndexOf(json('[1,1,3,5,null,3,7,3,9]'), null) ==> 4
Returns the first element of an array.
json('[7,1,9,null,5,3]').first() ==> 7
first(json('[null,7,1,9,5,3]')) ==> null
Returns the last element of an array.
json('[7,1,9,null,5,3]').last() ==> 3
last(json('[7,1,9,5,3,null]')) ==> null
Returns the element with the maximum value.
json('[7,1,9,null,5,3]').max() ==> 9
max(json('[7,1,9,null,5,3]'), 15, 16) ==> 16
Returns the element with the minimum value.
json('[7,1,9,null,5,3]').min() ==> 1
min(json('[7,1,9,null,5,3]'), 15, 16) ==> 1
Returns the first N elements when sorted in descending order.
json('[7,1,9,null,5,3]').topN(2) ==> [ 9, 7 ]
topN(json('[7,1,9,null,5,3]'), 6) ==> [ 9, 7, 5, 3, 1 ]
Returns the first N elements when sorted in ascending order.
json('[7,1,9,null,5,3]').bottomN(2) ==> [ 1, 3 ]
bottomN(json('[7,1,9,null,5,3]'), 6) ==> [ 1, 3, 5, 7, 9 ]
Returns the sum of all element values.
json('[7,1,9,null,5,3]').sum() ==> 25.0
sum(json('[7,1,9,null,5,3]'), 15, 16) ==> 56.0
Returns the average of all element values. Null value is not counted.
json('[7,1,9,null,5,3]').avg() ==> 5.0
avg(json('[7,1,9,null,5,3]'), 15, 16) ==> 8.0
Returns the count of non-null elements.
json('[7,1,9,null,5,3]').count() ==> 5
count(json('[7,1,9,null,5,3]'), 15, 16) ==> 7
Adds new elements to the end of an array.
json('[7,1,9,null,5,3]').push(10,'X',json('[-1,-2]'),json('{"a":11,"b":12}'))
==>
[ 7, 1, 9, null, 5, 3, 10, "X", [ -1, -2 ], {
"a" : 11,
"b" : 12
} ]
Reverses the order of elements in an array.
json('[7,1,9,null,5,3]').reverse() ==> [ 3, 5, null, 9, 1, 7 ]
reverse(json('[7,1,9,null,5,3]')) ==> [ 3, 5, null, 9, 1, 7 ]
Extracts a portion of an array.
json('[1,2,3,4,5,6,7,8,9]').slice(3) ==> [ 4, 5, 6, 7, 8, 9 ]
json('[1,2,3,4,5,6,7,8,9]').slice(2,8) ==> [ 3, 4, 5, 6, 7, 8 ]
json('[1,2,3,4,5,6,7,8,9]').slice(,5) ==> [ 1, 2, 3, 4, 5 ]
json('[1,2,3,4,5,6,7,8,9]').slice(-5) ==> [ 5, 6, 7, 8, 9 ]
json('[1,2,3,4,5,6,7,8,9]').slice(?,1,8,2) ==> [ 2, 4, 6, 8 ]
json('[1,2,3,4,5,6,7,8,9]').slice(?,2,,2) ==> [ 3, 5, 7, 9 ]
slice(json('[1,2,3,4,5,6,7,8,9]'),6,2,1) ==> [ 7, 6, 5, 4 ]
slice(json('[1,2,3,4,5,6,7,8,9]'),,,3) ==> [ 1, 4, 7 ]
slice(json('[1,2,3,4,5,6,7,8,9]'),,-5,1) ==> [ 1, 2, 3, 4 ]
Sorts the elements of an array.
json('[1,1,3,5,3,7,3,9]').sort() ==> [ 1, 1, 3, 3, 3, 5, 7, 9 ]
json('[1,1,3,5,3,7,3,9]').sort(?,-1) ==> [ 9, 7, 5, 3, 3, 3, 1, 1 ]
json('[{"seq":4,"val":"A"},{"seq":1,"val":"B"},{"seq":3,"val":"C"},{"seq":2,"val":"D"}]').sort(seq)
==>
[ {
"seq" : 1,
"val" : "B"
}, {
"seq" : 2,
"val" : "D"
}, {
"seq" : 3,
"val" : "C"
}, {
"seq" : 4,
"val" : "A"
} ]
json('[{"seq":4,"val":"A"},{"seq":1,"val":"B"},{"seq":3,"val":"C"},{"seq":2,"val":"D"}]').sort(seq,-1)
==>
[ {
"seq" : 4,
"val" : "A"
}, {
"seq" : 3,
"val" : "C"
}, {
"seq" : 2,
"val" : "D"
}, {
"seq" : 1,
"val" : "B"
} ]
Removes duplicate elements from an array.
json('[1,1,3,5,3,7,3,9]').distinct().sort() ==> [ 1.0, 3.0, 5.0, 7.0, 9.0 ]
distinct(json('["A","Z","a","Z","A","z"]')) ==> [ "A", "a", "Z", "z" ]
distinct(json('["1","1.0",1,1.0,1.00,true,"true",null,"null"]')) ==> [ "1", "1.0", "null", "true", 1.0, true ]
Joins all element values into a string.
json('["Hello", ",", "World", "!"]').join() ==> "Hello,World!"
json('[1,2,3]').join('+') ==> "1+2+3"
join(json('["A",1,"B","2.00","C",3.00,"D",true,null]'),'/') ==> "A/1/B/2.00/C/3.0/D/true"
Finds and modifies matching elements.
json('[{"code":"A","price":8},{"code":"B","price":8},{"code":"C","price":3}]').findAndModify([code='C'],field(price:99))
==>
[ {
"code" : "A",
"price" : 8
}, {
"code" : "B",
"price" : 8
}, {
"code" : "C",
"price" : 99
} ]
json('[{"code":"A","price":8},{"code":"B","price":8},{"code":"C","price":3}]').findAndModify([price=8],field(price:99),2)
==>
[ {
"code" : "A",
"price" : 99
}, {
"code" : "B",
"price" : 99
}, {
"code" : "C",
"price" : 3
} ]
Finds the element with the maximum value for a field.
json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]').findByMax(price)
==>
{
"code" : "A",
"price" : 8
}
findByMax(json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]'), code)
==>
{
"code" : "E",
"price" : 5
}
Finds the element with the minimum value for a field.
json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]').findByMin(?,price)
==>
{
"code" : "C",
"price" : 3
}
findByMin(json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]'), code)
==>
{
"code" : "A",
"price" : 8
}
Finds the first element with the field value is null. If not found, returns the element with the maximum value for a field.
json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]').findByNullOrMax(price)
==>
{
"code" : "B"
}
findByNullOrMax(json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]'), code)
==>
{
"code" : "E",
"price" : 5
}
Finds the first element with the field value is null. If not found, returns the element with the minimum value for a field.
json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]').findByNullOrMin(?,price)
==>
{
"code" : "B"
}
findByNullOrMin(json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]'), code)
==>
{
"code" : "A",
"price" : 8
}
Finds the element with the maximum value for a field. If only null field value exists, returns the first element.
json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]').findByMaxOrNull(price)
==>
{
"code" : "A",
"price" : 8
}
findByMaxOrNull(json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]'), code)
==>
{
"code" : "E",
"price" : 5
}
Finds the element with the minimum value for a field. If only null field value exists, returns the first element.
json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]').findByMinOrNull(?,price)
==>
{
"code" : "C",
"price" : 3
}
findByMinOrNull(json('[{"code":"A","price":8},{"code":"B"},{"code":"C","price":3},{"code":"D","price":8},{"code":"E","price":5}]'), code)
==>
{
"code" : "A",
"price" : 8
}
Returns an array of key-value entries for an object.
json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').entries()
==>
[ {
"key" : "a",
"value" : 1
}, {
"key" : "b",
"value" : [ 2, 3 ]
}, {
"key" : "c",
"value" : {
"d" : 4,
"e" : 5
}
} ]
Returns an array of keys for an object.
json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').keys() ==> [ "a", "b", "c" ]
json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').keys(2) ==> [ "a", "b", "c", "d", "e" ]
keys(json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}'), -1) ==> [ "a", "b", "c", "d", "e" ]
Limits the depth for objects in results.
json('{"id":1,"array":[{"id":2,"obj":{"id":3,"array":[{"id":4.1},{"id":4.2}]}}]}').depthLimit(1)
==>
{
"id" : 1
}
json('{"id":1,"array":[{"id":2,"obj":{"id":3,"array":[{"id":4.1},{"id":4.2}]}}]}').depthLimit(2)
==>
{
"id" : 1,
"array" : [ {
"id" : 2
} ]
}
depthLimit(json('{"id":1,"array":[{"id":2,"obj":{"id":3,"array":[{"id":4.1},{"id":4.2}]}}]}'),3)
==>
{
"id" : 1,
"array" : [ {
"id" : 2,
"obj" : {
"id" : 3
}
} ]
}
depthLimit(json('{"id":1,"array":[{"id":2,"obj":{"id":3,"array":[{"id":4.1},{"id":4.2}]}}]}'),4)
==>
{
"id" : 1,
"array" : [ {
"id" : 2,
"obj" : {
"id" : 3,
"array" : [ {
"id" : 4.1
}, {
"id" : 4.2
} ]
}
} ]
}
Collects values into a single array.
'Hi'.collect(1,?,true,json('[{"a":1,"x":11},{"b":2,"y":12}]'),json('{"c":3,"x":13}'))
==>
[ 1, "Hi", true, [ {
"a" : 1,
"x" : 11
}, {
"b" : 2,
"y" : 12
} ], {
"c" : 3,
"x" : 13
} ]
Collects values across nested structures.
The 1st parameter is a query to evaluate a result that will be collected into an array.
The 2nd parameter is a query to evaluate the next dataset that loop back for the 1st parameter evaluation again.
The operation loop will be stopped when the next dataset is null.
json('{"id":1,"val":11,"item":{"id":2,"val":22,"item":{"id":3,"val":33,"item":{"id":4,"val":44}}}}')
.cumulateCollect(map(id,val.calc(?*2)), item)
==>
[ {
"id" : 1,
"val" : 22.0
}, {
"id" : 2,
"val" : 44.0
}, {
"id" : 3,
"val" : 66.0
}, {
"id" : 4,
"val" : 88.0
} ]
Wraps a value into an array.
json('["Hi"]').wrap() ==> [ [ "Hi" ] ]
wrap(json('{"a":1}'))
==>
[ {
"a" : 1
} ]
Converts values to a flat array.
json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').toArray()
==>
[ 1, [ 2, 3 ], {
"d" : 4,
"e" : 5
} ]
json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').toArray(c) ==> [ 4, 5 ]
toArray(json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').toArray()) ==> [ 1, 2, 3, 4, 5 ]
Converts values to an object with named properties.
'a'.toObject('text')
==>
{
"text" : "a"
}
99.toObject('number')
==>
{
"number" : 99
}
json('[1,2,3]').toObject('array')
==>
{
"array" : [ 1, 2, 3 ]
}
json('{"a":1,"b":2}').toObject('obj')
==>
{
"obj" : {
"a" : 1,
"b" : 2
}
}
Merges nested arrays into a single array.
json('[[1,2],[3,4],[5,6]]').mergeArrays(?) ==> [ 1, 2, 3, 4, 5, 6 ]
json('[{"a":[1,2]},{"a":[3,4]},{"a":[5,6]}]').mergeArrays(a) ==> [ 1, 2, 3, 4, 5, 6 ]
json('{"a":[1,2],"b":[3,4],"c":[5,6]}').mergeArrays(a,b,c) ==> [ 1, 2, 3, 4, 5, 6 ]
Merges objects by replacing common keys.
json('[{"a":1,"x":11},{"b":2,"y":12},{"c":3,"x":13}]').mergeObjects()
==>
{
"a" : 1,
"x" : 13,
"b" : 2,
"y" : 12,
"c" : 3
}
mergeObjects(json('[{"a":1,"x":11},{"b":2,"y":12}]'), json('{"c":3,"x":13}'))
==>
{
"a" : 1,
"x" : 13,
"b" : 2,
"y" : 12,
"c" : 3
}
Flattens nested objects/arrays into a flat structure.
json('[[[[1,2],[3,4]],[[5,6],[7,8]]],[[[9,10],[11,12]],[[13,14],[15,16]]]]').flatten(1)
==>
[ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ], [ [ 9, 10 ], [ 11, 12 ] ], [ [ 13, 14 ], [ 15, 16 ] ] ]
json('[[[[1,2],[3,4]],[[5,6],[7,8]]],[[[9,10],[11,12]],[[13,14],[15,16]]]]').flatten(2)
==>
[ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ], [ 7, 8 ], [ 9, 10 ], [ 11, 12 ], [ 13, 14 ], [ 15, 16 ] ]
json('[[[[1,2],[3,4]],[[5,6],[7,8]]],[[[9,10],[11,12]],[[13,14],[15,16]]]]').flatten()
==>
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]
flatten(json('[[[[1,2],[3,4]],[[5,6],[7,8]]],[[[9,10],[11,12]],[[13,14],[15,16]]]]'), 3, null)
==>
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]
flatten(json('{"a":1,"b":[2,3],"c":{"d":4,"e":{"f":5}}}'), '_', null)
==>
{
"a" : 1,
"b_0" : 2,
"b_1" : 3,
"c_d" : 4,
"c_e_f" : 5
}
json('[0,1,[2,3,[4,{"a":5},6,[7]],8],9]').flatten('_')
==>
{
"0" : 0,
"1" : 1,
"2_0" : 2,
"2_1" : 3,
"2_2_0" : 4,
"2_2_1_a" : 5,
"2_2_2" : 6,
"2_2_3_0" : 7,
"2_3" : 8,
"3" : 9
}
flatten(json('{"a":1,"b":[2,3],"c":{"d":4,"e":{"f":5}}}'), '.', '[%d]')
==>
{
"a" : 1,
"b[0]" : 2,
"b[1]" : 3,
"c.d" : 4,
"c.e.f" : 5
}
json('[0,1,[2,3,[4,{"a":5},6,[7]],8],9]').flatten('.', '[%d]')
==>
{
"[0]" : 0,
"[1]" : 1,
"[2][0]" : 2,
"[2][1]" : 3,
"[2][2][0]" : 4,
"[2][2][1].a" : 5,
"[2][2][2]" : 6,
"[2][2][3][0]" : 7,
"[2][3]" : 8,
"[3]" : 9
}
Reverses a flatten operation on objects/arrays.
flatten(json('{"a":1,"b":[2,3],"c":{"d":4,"e":{"f":5}}}'),'_',null).unflatten('_')
==>
{
"a" : 1,
"b" : [ 2, 3 ],
"c" : {
"d" : 4,
"e" : {
"f" : 5
}
}
}
json('[0,1,[2,3,[4,{"a":5},6,[7]],8],9]').flatten('_').unflatten('_')
==>
[ 0, 1, [ 2, 3, [ 4, {"a":5}, 6, [ 7 ] ], 8 ], 9 ]
flatten(json('{"a":1,"b":[2,3],"c":{"d":4,"e":{"f":5}}}'),'.','[%d]').unflatten('.[]')
==>
{
"a" : 1,
"b" : [ 2, 3 ],
"c" : {
"d" : 4,
"e" : {
"f" : 5
}
}
}
json('[0,1,[2,3,[4,{"a":5},6,[7]],8],9]').flatten('.','[%d]').unflatten('.[]')
==>
[ 0, 1, [ 2, 3, [ 4, {"a":5}, 6, [ 7 ] ], 8 ], 9 ]
Projects each element into a new object.
json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').map(c.e,c.d,b,a)
==>
{
"e" : 5,
"d" : 4,
"b" : [ 2, 3 ],
"a" : 1
}
json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').map(cc:c.map(dd:d,ee:e),xx:map(aa:a,bb:b))
==>
{
"cc" : {
"dd" : 4,
"ee" : 5
},
"xx" : {
"aa" : 1,
"bb" : [ 2, 3 ]
}
}
Add, modify or delete element in an object.
json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').field(f:6,c:)
==>
{
"a" : 1,
"b" : [ 2, 3 ],
"f" : 6
}
json('{"id":"1782-734828-A","name":"Cyron"}').field(id.split('-')@.repeat('X',length()).@join('-'))
==>
{
"id" : "XXXX-XXXXXX-X",
"name" : "Cyron"
}
Groups elements into an object by a field.
json('[{"a":1,"b":"A"},{"a":2,"b":"B"},{"a":3,"b":"C"},{"a":2,"b":"D"},{"a":1,"b":"E"}]').group(a)
==>
[ {
"a" : 1,
"elements" : [ {
"a" : 1,
"b" : "A"
}, {
"a" : 1,
"b" : "E"
} ]
}, {
"a" : 2,
"elements" : [ {
"a" : 2,
"b" : "B"
}, {
"a" : 2,
"b" : "D"
} ]
}, {
"a" : 3,
"elements" : [ {
"a" : 3,
"b" : "C"
} ]
} ]
json('[{"a":1,"b":"A"},{"a":2,"b":"B"},{"a":3,"b":"C"},{"a":2,"b":"D"},{"a":1,"b":"E"}]').group(a,bs:b)
==>
[ {
"a" : 1,
"bs" : [ "A", "E" ]
}, {
"a" : 2,
"bs" : [ "B", "D" ]
}, {
"a" : 3,
"bs" : [ "C" ]
} ]
Unwinds grouped elements back into an array.
json('[{"a":1,"bs":["A","E"]},{"a":2,"bs":["B","D"]},{"a":3,"bs":["C"]}]').unwind(b:bs)
==>
[ {
"a" : 1,
"b" : "A"
}, {
"a" : 1,
"b" : "E"
}, {
"a" : 2,
"b" : "B"
}, {
"a" : 2,
"b" : "D"
}, {
"a" : 3,
"b" : "C"
} ]
Separates elements based on conditional assorting.
json('{"xy1": 1,"xy2": 2,"ab1": 3,"ab2": 4,"ab3": 5,"zz1": 6,"xy3": 7,"zz2": 9,"zz3": {"k":10}}}').assort(*.[isEven()], ~'xy.*', ~'ab.*', ??)
==>
[ {
"xy2" : 2,
"ab2" : 4,
"zz1" : 6
}, {
"xy1" : 1,
"xy3" : 7
}, {
"ab1" : 3,
"ab3" : 5
}, {
"zz2" : 9
}, {
"zz3" : {
"k" : 10
}
} ]
json('[1,2,3,4,5,6,7,8,9,10,11,12]').assort([?<5], [isEven()], [?<9], ?)
==>
[ [ 1, 2, 3, 4 ], [ 6, 8, 10, 12 ], [ 5, 7 ], [ 9, 11 ] ]
Evaluate a string value of a field as a Josson expression.
json('{"a":1,"b":2,"statement":"calc(a+b*2)"}').eval(statement) ==> 5.0
json('[{"a":3,"s":"calc(a*2)"},{"a":4,"s":"calc(a*2)"}]')@.eval(s) ==> [ 6.0, 8.0 ]
Parses a string as JSON.
json('[1,"2",{"a":1,"b":2}]')
==>
[ 1, "2", {
"a" : 1,
"b" : 2
} ]
'{"a":1,"b":[2,3],"c":{"d":4,"e":5}}'.json()
==>
{
"a" : 1,
"b" : [ 2, 3 ],
"c" : {
"d" : 4,
"e" : 5
}
}
Conditionally returns one value if expression is true, otherwise another value.
json('{"a":1,"b":2,"c":3}').if(a.isEven(), 'T', 'F') ==> "F"
json('{"a":1,"b":2,"c":3}').if([a=1], 'T', 'F') ==> "T"
json('{"a":1,"b":2,"c":3}').if([a=1 & b=3], 'T', 'F') ==> "F"
json('{"a":1,"b":2,"c":3}').if([a=1 & b=3], 'T') ==> !unresolvable!
json('{"a":1,"b":2,"c":3}').if([a=b], 'T', if([c=3], 'C', 'F')) ==> "C"
json('[1,2,3,4,5]').if(isOdd(), calc(?*2), ?) ==> [ 2.0, 2, 6.0, 4, 10.0 ]
Reverse of if(), returns value if expression is false.
json('{"a":1,"b":2,"c":3}').ifNot(a.isEven(), 'T', 'F') ==> "T"
json('{"a":1,"b":2,"c":3}').ifNot([a=1 & b=3], 'T') ==> "T"
json('{"a":1,"b":2,"c":3}').ifNot([a=b], 'T', if([c=3], 'C', 'F')) ==> "T"
json('[1,2,3,4,5]').ifNot(isOdd(), calc(?*2), ?) ==> [ 1, 4.0, 3, 8.0, 5 ]
Returns first non-null value from a list of values.
json('["abc","",123,false,null]').coalesce('xyz') ==> [ "abc", "", 123, false, "xyz" ]
json('{"a":null,"c":"abc"}').coalesce(a,b,c,'xyz') ==> "abc"
Returns value matching case, else default value.
'a'.caseValue('A',1,'b',2,'a',3,4) ==> 3
'z'.caseValue('A',1,'b',2,'a',3,4) ==> 4
'z'.caseValue('A',1,'b',2,'a',3) ==> !unresolvable!
json('[{"s":1},{"s":null},{"s":3}]').s.caseValue(1,'A',null,'B') ==> [ "A", "B", null ]
Same as above but ignores case sensitivity.
'a'.caseValue('A',1,'b',2,'a',3,4) ==> 1
'z'.caseValue('A',1,'b',2,'a',3,4) ==> 4
'z'.caseValue('A',1,'b',2,'a',3) ==> !unresolvable!
Returns value at an index from an array or list of parameters.
0.indexedValue('a','b','c','d') ==> "a"
1.indexedValue(json('["a","b","c","d"]')) ==> "b"
'3'.indexedValue('a','b','c','d') ==> "d"
4.indexedValue('a','b','c','d') ==> !unresolvable!
-1.indexedValue('a','b','c','d') ==> !unresolvable!
Returns value cycling through an array or list of parameters circularly.
0.cycleValue('a','b','c','d') ==> "a"
1.cycleValue(json('["a","b","c","d"]')) ==> "b"
'3'.cycleValue('a','b','c','d') ==> "d"
4.cycleValue('a','b','c','d') ==> "a"
-1.cycleValue('a','b','c','d') ==> "d"
-6.cycleValue('a','b','c','d') ==> "c"
Returns the number of path steps processed.
json('{"a":{"b":{"c":1}}}').a.steps() ==> 2
json('{"a":{"b":{"c":1}}}').a.b.steps() ==> 3
json('{"a":{"b":{"c":1}}}').*().c.steps() ==> 4
json('{"a":{"b":{"c":1}}}').a.b.calc(c+1).steps() ==> 4
json('{"a":{"b":{"c":1}}}').a.b.c.calc(?+1).steps() ==> 5
Returns the evaluated value of a path based on the current node.
json('{"decode":[{"code":"A","color":"Red"},{"code":"B","color":"Blue"}],"data":["B","A","B"]}')
[email protected]($code:?).get(...decode[code=$code].color)
==>
[ "Blue", "Red", "Blue" ]
Defines variables for the forthcoming path steps.
This function does not count as a step.
json('{"a":1,"b":2}').let($x:a, $y:calc(a+b), $z:concat(a,b)).map($x,$y,$z)
==>
{
"$x" : 1,
"$y" : 3.0,
"$z" : "12"
}
Set the merge arrays method. Available options are the following, case insensitive:
This function does not count as a step.
json('[[1,2,3,4,5,6],[8,6,4,2]]').mergeArraysOption(Append).mergeArrays() ==> [ 1, 2, 3, 4, 5, 6, 8, 6, 4, 2 ]
json('[[1,2,3,4,5,6],[8,6,4,2]]').mergeArraysOption(Integrate).mergeArrays() ==> [ 1, 2, 3, 4, 5, 6, 8 ]
json('[[1,2,3,4,5,6],[8,6,4,2]]').mergeArraysOption(ReplaceWhole).mergeArrays() ==> [ 8, 6, 4, 2 ]
json('[[1,2,3,4,5,6],[8,6,4,2]]').mergeArraysOption(ReplaceByIndex).mergeArrays() ==> [ 8, 6, 4, 2, 5, 6 ]
json('[[8,6,4,2],[1,2,3,4,5,6]]').mergeArraysOption(Append).mergeArrays() ==> [ 8, 6, 4, 2, 1, 2, 3, 4, 5, 6 ]
json('[[8,6,4,2],[1,2,3,4,5,6]]').mergeArraysOption(Integrate).mergeArrays() ==> [ 8, 6, 4, 2, 1, 3, 5 ]
json('[[8,6,4,2],[1,2,3,4,5,6]]').mergeArraysOption(ReplaceWhole).mergeArrays() ==> [ 1, 2, 3, 4, 5, 6 ]
json('[[8,6,4,2],[1,2,3,4,5,6]]').mergeArraysOption(ReplaceByIndex).mergeArrays() ==> [ 1, 2, 3, 4, 5, 6 ]
json('[{\"a\":[1,2,3,4,5,6],\"b\":\"1st\"},{\"a\":[8,6,4,2],\"b\":\"2nd\"}]').mergeArraysOption(Append).mergeObjects()
==>
{
"a" : [ 1, 2, 3, 4, 5, 6, 8, 6, 4, 2 ],
"b" : "2nd"
}
json('[{\"a\":[1,2,3,4,5,6],\"b\":\"1st\"},{\"a\":[8,6,4,2],\"b\":\"2nd\"}]').mergeArraysOption(Integrate).mergeObjects()
==>
{
"a" : [ 1, 2, 3, 4, 5, 6, 8 ],
"b" : "2nd"
}
json('[{\"a\":[1,2,3,4,5,6],\"b\":\"1st\"},{\"a\":[8,6,4,2],\"b\":\"2nd\"}]').mergeArraysOption(ReplaceWhole).mergeObjects()
==>
{
"a" : [ 8, 6, 4, 2 ],
"b" : "2nd"
}
json('[{\"a\":[1,2,3,4,5,6],\"b\":\"1st\"},{\"a\":[8,6,4,2],\"b\":\"2nd\"}]').mergeArraysOption(ReplaceByIndex).mergeObjects()
==>
{
"a" : [ 8, 6, 4, 2, 5, 6 ],
"b" : "2nd"
}
json('[{\"a\":[8,6,4,2],\"b\":\"2nd\"},{\"a\":[1,2,3,4,5,6],\"b\":\"1st\"}]').mergeArraysOption(Append).mergeObjects()
==>
{
"a" : [ 8, 6, 4, 2, 1, 2, 3, 4, 5, 6 ],
"b" : "1st"
}
json('[{\"a\":[8,6,4,2],\"b\":\"2nd\"},{\"a\":[1,2,3,4,5,6],\"b\":\"1st\"}]').mergeArraysOption(Integrate).mergeObjects()
==>
{
"a" : [ 8, 6, 4, 2, 1, 3, 5 ],
"b" : "1st"
}
json('[{\"a\":[8,6,4,2],\"b\":\"2nd\"},{\"a\":[1,2,3,4,5,6],\"b\":\"1st\"}]').mergeArraysOption(ReplaceWhole).mergeObjects()
==>
{
"a" : [ 1, 2, 3, 4, 5, 6 ],
"b" : "1st"
}
json('[{\"a\":[8,6,4,2],\"b\":\"2nd\"},{\"a\":[1,2,3,4,5,6],\"b\":\"1st\"}]').mergeArraysOption(ReplaceByIndex).mergeObjects()
==>
{
"a" : [ 1, 2, 3, 4, 5, 6 ],
"b" : "1st"
}
You can define your own custom functions to manipulate a node in a Josson query.
Customer function name must starts with “$” and ends with “()”.
There are 2 types of custom function:
Example
$randomUuid() - return a random UUID
$addIndex() - add the array index value to the base
integer value
Josson josson = Josson.fromJsonString(
“[” +
" {“base”: 2}," +
" {“base”: 3}," +
" {“base”: 5}" +
“]”
)
.customFunction(
“$randomUuid()”,
(node) -> TextNode.valueOf(UUID.randomUUID().toString())
)
.customFunction(
“$addIndex()”,
(node, index) -> IntNode.valueOf(node.get(“base”).asInt() + index)
);
JsonNode node = josson.getNode(“field(id:$randomUuid(), base+index:$addIndex())”);
System.out.println(node.toPrettyString());
Output
[ {
"base" : 2,
"id" : "9dbb0ed2-83cb-4faa-9920-b02f4cb1bddb",
"base+index" : 2
}, {
"base" : 3,
"id" : "5ad685bb-06f4-4574-8e1f-a9fb4a8609cb",
"base+index" : 4
}, {
"base" : 5,
"id" : "1c499b97-2b9c-494f-b935-0c97c7401890",
"base+index" : 7
} ]
Jossons stores JSON datasets in a map of type Map<String, Josson>
for placeholder resolution.
To create a Jossons object without data.
Jossons jossons = new Jossons();
To create a Jossons object with given Jackson ObjectNode.
Each entry under root of the ObjectNode will become a member of the default dataset mapping.
Jossons jossons = Jossons.create(jsonNode);
To create a Jossons object with given JSON string that deserialized to a Jackson ObjectNode.
Each entry under root of the ObjectNode will become a member of the default dataset mapping.
Jossons jossons = Jossons.fromJsonString("{...}");
To create a Jossons object with given text-based dataset mapping Map<String, String>
.
Jossons jossons = Jossons.fromMap(mapping);
To create a Jossons object with given integer-based dataset mapping Map<String, Integer>
.
Jossons jossons = Jossons.fromMapOfInt(mapping);
To add more default dataset entry to a Jossons object afterward.
jossons.putDataset("key", josson);
To tell Jossons what markup language escaping operation is required.
Default is MarkupLanguage.NONE
for plain text.
jossons.escapingMarkup(MarkupLanguage.XML);
jossons.escapingMarkup(MarkupLanguage.HTML);
A placeholder is enclosed by double curly braces.
A template is a text based document layout with Jossons placeholders.
It can be any format, such as plain text, HTML or XML.
To present a dataset entry’s value content as text.
{{key}}
To apply a Josson Query on a dataset entry’s value.
{{key->query}}
Placeholders can be nested and are resolved from inside to outside.
Resolved placeholder is replaced with text and continue for the next round.
Example
|<----------------------------------------------- 3 --------------------------------->|
| |<---------------------------- 2 -------------------------->| |
| | |<------ 1 ------>| | |
| | | | | |
{{stock->[itemCode={{order->items[qrCode={{qrCode->quote()}}].itemCode.quote()}}].qty}}
{{qrCode->quote()}}
is resolved to '1234567890'
{{order->items[qrCode='1234567890'].itemCode.quote()}}
is resolved to 'ABCDE'
{{stock->[itemCode='ABCDE'].qty}}
is resolved to 100
The resolved text value is allowed to contain {
and }
.
Any combination of these symbols in the text will not influence the next round of template placeholder resolution.
This mechanism can prevent injection attack.
The ternary pattern can be repeated with no limit.
{{boolean ? trueValue : falseValue}}
{{boolean ? trueValue : boolean ? trueValue : falseValue}}
{{boolean ? trueValue ⟬: boolean ? trueValue⟭ⁿ : falseValue}}
If all conditions are evaluated to be false and falseValue
is not given, it returns an empty string.
{{boolean ? trueValue}}
{{boolean ? trueValue : boolean ? trueValue}}
{{boolean ? trueValue ⟬: boolean ? trueValue⟭ⁿ }}
Syntax ?:
is much like coalesce()
but the checking conditions of valueAsText
are unresolvable and empty string in addition to null node.
{{valueAsText ?: valueAsText}}
{{valueAsText ⟬?: valueAsText⟭ⁿ }}
If valueAsText
is unresolvable, valueAsText?
returns an empty string instead of throws NoValuePresentException
.
The following two statements have the same result.
{{valueAsText?}}
{{valueAsText ?: ''}}
The above syntax can be mixed in a placeholder. For example:
{{boolean ? trueValue : anotherValue ?: anotherAnotherValue?}}
Josson query works on single JSON dataset.
In order to let a placeholder output to include data from two datasets.
It is required to use join operation to build a new dataset for the placeholder.
At least one matching key must be given and the number of key on both side must be the same.
Join operations match keyL1
with keyR1
, keyL2
with keyR2
and so on.
There are two join types for left and right join.
For Join Many operations, the arrayName:
is optional.
If arrayName
is not given, the last element name of the query is used.
Inner Join >=<
"leftQuery{keyL1,keyL2...} >=< rightQuery{keyR1,keyR2...}"
Left Join One <=<
"leftQuery{keyL1,keyL2...} <=< rightQuery{keyR1,keyR2...}"
Right Join One >=>
"leftQuery{keyL1,keyL2...} >=> rightQuery{keyR1,keyR2...}"
Left Join Many <=<<
"leftQuery{keyL1,keyL2...} <=<< rightQuery{arrayName:keyR1,keyR2...}"
Right Join Many >>=>
"leftQuery{arrayName:keyL1,keyL2...} >>=> rightQuery{keyR1,keyR2...}"
Left Excluding Join <!<
"leftQuery{keyL1,keyL2...} <!< rightQuery{keyR1,keyR2...}"
Right Excluding Join >!>
"leftQuery{keyL1,keyL2...} >!> rightQuery{keyR1,keyR2...}"
Outer Excluding Join <!>
"leftQuery{keyL1,keyL2...} <!> rightQuery{keyR1,keyR2...}"
Set operations do not need matching key.
Left Concatenate <+<
Concatenate right into left. Works on two objects or two arrays.
If one is object and the other is array, the object will be put into an array before manipulation.
"leftQuery <+< rightQuery"
Right Concatenate >+>
Concatenate left into right. Works on two objects or two arrays.
If one is object and the other is array, the object will be put into an array before manipulation.
"leftQuery >+> rightQuery"
Subtract Right From Left <-<
Set of elements in the left set that are not in the right set. Works on two objects or two arrays.
If one is object and the other is array, the object will be put into an array before manipulation.
"leftQuery <-< rightQuery"
Subtract Left From Right >->
Set of elements in the right set that are not in the left set. Works on two objects or two arrays.
If one is object and the other is array, the object will be put into an array before manipulation.
"leftQuery >-> rightQuery"
Symmetric Difference <->
Set of elements in either set but not in the intersection. Works on two objects or two arrays.
"leftQuery <-> rightQuery"
Union <u>
Set of all elements in the collection of sets. Works on two arrays.
"leftQuery <u> rightQuery"
Intersection >n<
Set of elements that exists in both set. Works on two arrays.
"leftQuery >n< rightQuery"
The chaining Pipe is used to perform multiple join and set operations within a single expression.
This chaining operation will be chained using the pipe operator |
.
... | {keyL1,keyL2...} <JoinOperator> query{keyR1,keyR2...} | ...
... | <SetOperator> query | ...
Example
"queryA{aKey1} >=> queryB{bKey1} | <+< queryC | {cKey1,cKey2} <=<< queryD{arrayName:dKey1,dKey2}"
Key $
returns a BooleanNode
with true
value.
Key $now
returns a TextNode
of now with date and time. e.g. 2022-01-01T19:34:47.787144100
Key $today
returns a TextNode
of today’s date. e.g. 2022-01-01T00:00
Key $yesterday
returns a TextNode
of yesterday’s date. e.g. 2021-12-31T00:00
Key $tomorrow
returns a TextNode
of tomorrow’s date. e.g. 2022-01-02T00:00
Key $params
returns an ArrayNode
of a Dictionary Function’s parameters in an array.
Key $0
, $1
, $2
… returns a JsonNode
of a Dictionary Function’s individual parameter naming in zero-based index.
Below is the JSON for this tutorial.
The created Jossons object’s dataset map has two entries where the keys are “order” and “company”.
{
"order": {
"salesOrderId": "SO0001",
"salesDate": "2022-01-01T10:01:23",
"salesPerson": "Raymond",
"customer": {
"customerId": "CU0001",
"name": "Peggy",
"phone": "+852 62000610"
},
"items": [
{
"itemCode": "B00001",
"name": "WinWin TShirt Series A - 2022",
"brand": "WinWin",
"property": {
"size": "M",
"colors": [
"WHITE",
"RED"
]
},
"qty": 2,
"unit": "Pcs",
"unitPrice": 15.0,
"tags": [
"SHIRT",
"WOMEN"
]
},
{
"itemCode": "A00308",
"name": "OctoPlus Tennis Racket - Star",
"brand": "OctoPlus",
"property": {
"colors": [
"BLACK"
]
},
"qty": 1,
"unit": "Pcs",
"unitPrice": 150.0,
"unitDiscount": 10.0,
"tags": [
"TENNIS",
"SPORT",
"RACKET"
]
},
{
"itemCode": "A00201",
"name": "WinWin Sport Shoe - Super",
"brand": "WinWin",
"property": {
"size": "35",
"colors": [
"RED"
]
},
"qty": 1,
"unit": "Pair",
"unitPrice": 110.0,
"unitDiscount": 10.0,
"tags": [
"SHOE",
"SPORT",
"WOMEN"
]
}
],
"totalAmount": 270.0,
"discountPct": 5.0,
"netAmount": 256.5,
"delivery": {
"handlingFee": 5.0,
"address": "Wo Mun Street,\nFanling, N.T.,\nHong Kong",
"contactPerson": "Cyron",
"phone": "+852 26004198"
}
},
"company": {
"name": "Octomix Limited",
"phone": "+852 12345678",
"website": "www.octomix.com",
"address": [
"888 Queen's Road East",
"Hong Kong"
]
}
}
Function fillInPlaceholder()
uses the stored dataset mapping to merge and fill all placeholders in a template.
Any unresolvable placeholder will raise NoValuePresentException
with the incomplete merged text content.
All unresolvable placeholders are quoted with **
to replace the original double curly braces.
Jossons jossons = Jossons.fromJsonString(orderJsonString);
String output = jossons.fillInPlaceholder(template);
Template
"{{company->name.rightPad(65)}}INVOICE\n\n" +
"{{company->address[0].rightPad(56) ?: $->repeat(' ',56)}}Issue Date: {{order->salesDate.formatDate('dd/MM/yyyy')}}\n" +
"{{company->address[1].rightPad(58) ?: $->repeat(' ',58)}}Invoice#: {{order->salesOrderId.center(10)}}\n" +
"Phone: {{company->phone.rightPad(48)}}Customer ID: {{order->customer.customerId.center(10)}}\n" +
"Website: {{company->website.rightPad(49)}}Due Date: {{order->salesDate.plusMonths(1).formatDate('dd/MM/yyyy')}}\n\n" +
"BILL TO {{order->delivery!=null ? 'SHIP TO'}}\n" +
"{{order->customer.name.rightPad(30)}} {{order->delivery!=null ? order->coalesce(delivery.contactPerson,customer.name)}}\n" +
"{{order->customer.coalesce(phone,'N/A').concat('Phone: ',?).rightPad(30)}} " +
"{{order->delivery!=null ? order->coalesce(delivery.phone,customer.phone,'N/A').concat('Phone: ',?)}}\n" +
"{{order->delivery.address!=null ? order->delivery.address.split('\n').concat(repeat(' ',31),?).join('\n').concat(?,'\n')}}\n" +
"Item# Description Quantity Unit Price Discount Total\n" +
"----- ----------------------------------- -------- ---------- -------- --------\n" +
"{{order->items.concat(" +
" ##.center(5),' '," +
" name.rightPad(35),' '," +
" concat(qty,' ',unit).center(8),' '," +
" unitPrice.formatNumber('#,##0.0').leftPad(9),' '," +
" coalesce(unitDiscount,0).formatNumber('#,##0.0').leftPad(8),' '," +
" calc(qty * (unitPrice-d), d:coalesce(unitDiscount,0)).formatNumber('#,##0.0').leftPad(9)," +
" '\n ',itemCode,' '," +
" property.entries().concat(key,':',value.toString()).join(' ')" +
" ).join('\n')" +
"}}\n" +
"----- ----------------------------------- -------- ---------- -------- --------\n" +
"{{order->totalAmount.formatNumber('US$#,##0.0').leftPad(12).concat('Subtotal:',?,'\n').leftPad(80)}}" +
"{{order->discountPct > 0 ? order->discountPct.formatNumber('0.0').leftPad(11).concat('Discount:',?,'%\n').leftPad(80)}}" +
"{{order->delivery.handlingFee!=null ? order->delivery.handlingFee.formatNumber('US$#,##0.0').leftPad(12).concat('Shipping and handling:',?,'\n').leftPad(80)}}" +
"{{order->calc(netAmount+fee, fee:coalesce(delivery.handlingFee,0)).formatNumber('US$#,##0.0').leftPad(12).concat('Total:',?,'\n').leftPad(80)}}"
If company->address[0]
is unresolvable, 56 spaces are printed.
{{company->address[0].rightPad(56) ?: $->repeat(' ',56)}}
Due date is calculated from one month after the salesDate
.
{{order->salesDate.plusMonths(1).formatDate('dd/MM/yyyy')}}
“SHIP TO” is not printed if order->delivery
does not exists.
{{order->delivery!=null ? 'SHIP TO'}}
Delivery contact person is printed only if order->delivery
is defined.
If order->delivery.contactPerson
does not exists, order->customer.name
is printed instead.
{{order->delivery!=null ? order->coalesce(delivery.contactPerson,customer.name)}}
If order->delivery.address
exists, split it with delimiter \n
.
Then add 31 spaces in front of each line and join them together with \n
.
At last, add an extra \n
at the end.
{{order->delivery.address!=null ? order->delivery.address.split('\n').concat(repeat(' ',31),?).join('\n').concat(?,'\n')}}
Path chart
order → delivery{} → address → split(?) → [""] → [concat(?) ⇒ ""] → join(?[]) ⇒ ""
Construct two lines for each item. Each item amount is calculated from qty
, unitPrice
and unitDiscount
.
{{
order->items.concat(
##.center(5), ' ',
name.rightPad(35), ' ',
concat(qty,' ',unit).center(8), ' ',
unitPrice.formatNumber('#,##0.0').leftPad(9), ' ',
coalesce(unitDiscount,0).formatNumber('#,##0.0').leftPad(8), ' ',
calc(qty * (unitPrice-d), d:coalesce(unitDiscount,0)).formatNumber('#,##0.0').leftPad(9),
'\n ', itemCode, ' ',
property.entries().concat(key,':',value.toString()).join(' ')
).join('\n')
}}
Path chart
order → items[]* → [{}] → [concat(%) ⇒ ""] → join(?[]) ⇒ ""
If order->discountPct
is not > 0, the discount line is not printed.
{{order->discountPct > 0 ? order->discountPct.formatNumber('0.0').leftPad(11).concat('Discount:',?,'%\n').leftPad(80)}}
Order total amount is calculated by adding netAmount
and delivery.handlingFee
.
{{order->calc(netAmount+fee, fee:coalesce(delivery.handlingFee,0)).formatNumber('US$#,##0.0').leftPad(12).concat('Total:',?,'\n').leftPad(80)}}
Output
Octomix Limited INVOICE
888 Queen's Road East Issue Date: 01/01/2022
Hong Kong Invoice#: SO0001
Phone: +852 12345678 Customer ID: CU0001
Website: www.octomix.com Due Date: 01/02/2022
BILL TO SHIP TO
Peggy Cyron
Phone: +852 62000610 Phone: +852 26004198
32 Wo Mun Street,
Fanling, N.T.,
Hong Kong
Item# Description Quantity Unit Price Discount Total
----- ----------------------------------- -------- ---------- -------- --------
1 WinWin TShirt Series A - 2022 2 Pcs 15.0 0.0 30.0
B00001 size:M colors:["WHITE","RED"]
2 OctoPlus Tennis Racket - Star 1 Pcs 150.0 10.0 140.0
A00308 colors:["BLACK"]
3 WinWin Sport Shoe - Super 1 Pair 110.0 10.0 100.0
A00201 size:35 colors:["RED"]
----- ----------------------------------- -------- ---------- -------- --------
Subtotal: US$270.0
Discount: 5.0%
Shipping and handling: US$5.0
Total: US$261.5
Function fillInPlaceholderWithResolver()
uses the stored dataset mapping and with the help of on demand callback
dataset resolver to merge and fill all placeholders in a template.
String output = jossons.fillInPlaceholderWithResolver(template, dictionaryFinder, dataFinder, progress);
The last parameter progress
is a ResolverProgress
which record all the resolution progress steps.
By default, the last step “End” is added automatically.
The resolution process has three debug levels:
ResolverDebugLevel.SHOW_CONTENT_OF_VALUE_NODE_ONLY
(default)ResolverDebugLevel.SHOW_CONTENT_UP_TO_OBJECT_NODE
ResolverDebugLevel.SHOW_CONTENT_UP_TO_ARRAY_NODE
Basic constructors and methods:
ResolverProgress progress = new ResolverProgress();
ResolverProgress progress = new ResolverProgress("subject");
progress.debugLevel(ResolverDebugLevel.SHOW_CONTENT_UP_TO_OBJECT_NODE);
progress.autoMarkEnd(false);
List<String> steps = progress.getSteps();
If a key cannot be found in the default dataset mapping during the placeholder resolution process,
the resolver will ask Function<String, String> dictionaryFinder
for an answer.
dictionaryFinder
takes an argument String key
and returns a resolution statement of either:
A statement that represent a value.
"1" // IntNode
"2.3" // DoubleNode
"'Text'" // TextNode
"true" // BooleanNode
"null" // NullNode
Parse a JSON string of an object or array directly.
"{\"B00001\":2, \"A00308\":1, \"A00201\":1}" // ObjectNode
"[{\"B00001\":2}, {\"A00308\":1}, {\"A00201\":1}]" // ArrayNode
A Jossons query that retrieve data from other dataset.
"otherKey->jossonQuery"
A Jossons query with ternary syntax, please refer to Ternary Syntax.
"statement ? otherKey1->jossonQuery : otherKey2->jossonQuery"
A join operation to merge two datasets, please refer to Join Operation.
"leftQuery{keyL1,keyL2...} <=< rightQuery{keyR1,keyR2...}"
A set operation on two datasets, please refer to Set Operation.
"leftQuery <u> rightQuery"
A database query statement, please refer to Data Finder.
"collectionName ? {findStatement}"
Resolved result will be cached with the key name except for key name starts with $
.
Next time a placeholder or statement query for the same key will return the cached value without evaluation.
After Dictionary Finder returned a valid database query statement,
resolver will further trigger BiFunction<String, String, Josson> dataFinder
callback.
dataFinder
takes two arguments String collectionName
and String query
, and returns a Josson object.
One-document query syntax that request for an ObjectNode
:
"collectionName ? {findStatement}"
"collectionName ? [aggregateStatements]"
"? {findStatement}"
"? [aggregateStatements]"
Many-documents query syntax that request for an ArrayNode
:
"collectionName[] ? {findStatement}"
"collectionName[] ? [aggregateStatements]"
"[] ? {findStatement}"
"[] ? [aggregateStatements]"
collectionName
is optional. If not given, the resolving key will be passed to dataFinder
in the collection name argument.
For Many-documents query request, the collection name argument has a suffix of []
.
Appendix has an example of MongoDB adapter for this Data Finder.
If a dictionaryFinder
key ends with ()
, then it is a dictionary function.
It’s resolution statement can contain the following implicit variables.
$params
the calling statement’s parameters in an array.$0
, $1
, $2
… the calling statement’s individual parameter naming in zero-based index.If it is necessary to pass the values in an array node as the function parameters in the form of (elem0, elem1, elem2…),
use placeholder to transform by Josson function csvParams().
"customFunction({{arrayNode->csvParams()}})"
Dictionary finder entries
"double()" : "$0->calc(?*2)"
"sum2num()" : "$->calc({{$0}} + {{$1}})"
"sum2numThenDouble()" : "double(sum2num({{$0}},{{$1}}))->formatText('({{$0}}+{{$1}})x2 = %.1f')"
"projectName()" : "$0='CHI' ? '早晨' : 'Josson'"
"titledList()" : "$params->slice(1).concat(##,'. ',?).join('\n').concat({{$0->quote()}},'\n',{{$0->repeat('=',length()).quote()}},'\n',?)"
Placeholders
{{double(3)}} ==> "6.0"
{{sum2num(4,5)}} ==> "9.0"
{{sum2numThenDouble(1,2)}} ==> "(1+2)x2 = 6.0"
{{projectName()}} ==> "Josson"
{{projectName('CHI')}} ==> "早晨"
{{projectName('ENG')}} ==> "Josson"
{{titledList('List Title','Item A','Item B','Item C')}}
==>
List Title
==========
1. Item A
2. Item B
3. Item C
Map<String, String> dictionaryFinder = new HashMap<>();
dictionaryFinder.put("stocks", "[]?{ignoredQuery}");
dictionaryFinder.put("withStock", "order->items.map(itemCode,qty){itemCode} <=< stocks{itemCode}");
BiFunction<String, String, Josson> dataFinder = (collectionName, ignoredQuery) -> {
try {
if (collectionName.equals("stocks[]")) {
// Hardcode instead of database query
return Josson.fromJsonString("[" +
"{\"itemCode\":\"A00201\",\"onhandQty\":18}," +
"{\"itemCode\":\"A00308\",\"onhandQty\":76}," +
"{\"itemCode\":\"A00543\",\"onhandQty\":5}," +
"{\"itemCode\":\"B00001\",\"onhandQty\":231}," +
"{\"itemCode\":\"B00002\",\"onhandQty\":0}]");
}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
};
ResolverProgress progress = new ResolverProgress();
// Use the JSON data from section "Fill in"
Jossons jossons = Jossons.fromJsonString(orderJsonString);
String output = jossons.fillInPlaceholderWithResolver(
"Order ID : {{order->salesOrderId}}\n" +
"{{withStock->concat(itemCode.rightPad(10), 'Qty: ', qty, ' Onhand: ', onhandQty).join('\n')}}",
dictionaryFinder::get, dataFinder, progress));
Output
Order ID : SO0001
B00001 Qty: 2 Onhand: 231
A00308 Qty: 1 Onhand: 76
A00201 Qty: 1 Onhand: 18
Progress Steps
Round 1 : Resolving withStock from order->items.map(itemCode,qty){itemCode} <=< stocks{itemCode}
Round 2 : Matched stocks to data query []?{ignoredQuery}
Round 2 : Resolved stocks to Array with 5 elements
Round 2 : Resolved withStock = Array with 3 elements
Round 3 : End
Customize a BSON to JSON converter.
import org.bson.Document;
import org.bson.json.Converter;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriterSettings;
import org.bson.json.StrictJsonWriter;
import org.bson.types.ObjectId;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
public class Converters {
private static class ObjectIdConverter implements Converter<ObjectId> {
public static final ObjectIdConverter INSTANCE = new ObjectIdConverter();
@Override
public void convert(ObjectId value, StrictJsonWriter writer) {
writer.writeString(value.toHexString());
}
}
private static class EpochToLocalDateTimeConverter implements Converter<Long> {
public static final EpochToLocalDateTimeConverter INSTANCE = new EpochToLocalDateTimeConverter();
@Override
public void convert(Long value, StrictJsonWriter writer) {
LocalDateTime date = LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneId.of("Asia/Hong_Kong"));
writer.writeString(date.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}
}
private static final JsonWriterSettings JSON_WRITER_SETTINGS = JsonWriterSettings
.builder()
.outputMode(JsonMode.RELAXED)
.objectIdConverter(ObjectIdConverter.INSTANCE)
.dateTimeConverter(EpochToLocalDateTimeConverter.INSTANCE)
.build();
public static String bsonToJson(Document bson) {
return bson == null ? null : bson.toJson(JSON_WRITER_SETTINGS);
}
public static String bsonListToJson(List<Document> bsonList) {
return bsonList == null || bsonList.isEmpty() ? null :
"[" + bsonList.stream()
.map(Converters::bsonToJson)
.collect(Collectors.joining(",")) +
"]";
}
}
Define dataFinder()
. Use MongoTemplate
to query MongoDB directly.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.octomix.josson.Josson;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.codecs.BsonArrayCodec;
import org.bson.codecs.DecoderContext;
import org.bson.json.JsonReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
@Repository
public class JsonDao {
@Autowired
private MongoTemplate mongoTemplate;
public List<Document> findDocuments(String collectionName, String query) {
return mongoTemplate.find(
new BasicQuery(query),
Document.class,
collectionName
);
}
public List<Document> aggregateDocuments(String collectionName, String operations) {
List<BsonDocument> pipeline = new BsonArrayCodec()
.decode(new JsonReader(operations), DecoderContext.builder().build())
.stream()
.map(BsonValue::asDocument)
.collect(Collectors.toList());
return mongoTemplate.getDb().getCollection(collectionName).aggregate(pipeline).into(new ArrayList<>());
}
public String findJsonString(String collectionName, String query) {
return bsonToJson(mongoTemplate.findOne(
new BasicQuery(query),
Document.class,
collectionName
));
}
public String aggregateJsonString(String collectionName, String operations) {
List<Document> documents = aggregateDocuments(collectionName, operations);
return documents.isEmpty() ? null : bsonToJson(documents.get(0));
}
public BiFunction<String, String, Josson> dataFinder() {
return (collectionName, query) -> {
if (collectionName.endsWith("[]")) {
collectionName = collectionName.substring(0, collectionName.length()-2);
List<Document> documents = query.charAt(0) == '['
? aggregateDocuments(collectionName, query)
: findDocuments(collectionName, query);
if (!documents.isEmpty()) {
ArrayNode array = Josson.createArrayNode();
documents.stream()
.map(Converters::bsonToJson)
.forEach(json -> {
try {
array.add(Josson.readJsonNode(json));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
});
return Josson.create(array);
}
} else {
String json = query.charAt(0) == '['
? aggregateJsonString(collectionName, query)
: findJsonString(collectionName, query);
if (json != null) {
try {
return Josson.fromJsonString(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
return null;
};
}
}
(55934043)
[{
"file": [{
"fileRefNo": "AG/CSD/1",
"status": "Active"
}],
"requestNo": "225V49"
}, {
"file": [{
"fileRefNo": "AG/CSD/1",
"status": "Inactive"
}],
"requestNo": "225SRV"
}, {
"file": [{
"fileRefNo": "AG/CSD/2",
"status": "Active"
}],
"requestNo": "225SRV"
}]
Josson Query
group(requestNo, file).field(file.flatten(1))
Output
[ {
"requestNo" : "225V49",
"file" : [ {
"fileRefNo" : "AG/CSD/1",
"status" : "Active"
} ]
}, {
"requestNo" : "225SRV",
"file" : [ {
"fileRefNo" : "AG/CSD/1",
"status" : "Inactive"
}, {
"fileRefNo" : "AG/CSD/2",
"status" : "Active"
} ]
} ]
(73362526)
{
"success": 1,
"results": [
{
"FI": "120986750",
"event_id": "5164306",
"cards": {
"updated_at": "1660559432",
"key": "AAA100",
"sp": {
"cards": {
"id": "1",
"name": "Cards",
"odds": [
{
"id": "101",
"odds": "2.200",
"header": "Over",
"name": "11"
},
{
"id": "102",
"odds": "8.500",
"header": "Exactly",
"name": "11"
},
{
"id": "103",
"odds": "1.909",
"header": "Under",
"name": "11"
}
]
}
}
},
"corners": {
"updated_at": "1660559431",
"key": "AAA200",
"sp": {
"corners": {
"id": "2",
"name": "Corners",
"odds": [
{
"id": "201",
"odds": "2.200",
"header": "Over",
"name": "10"
},
{
"id": "202",
"odds": "8.500",
"header": "Exactly",
"name": "10"
},
{
"id": "203",
"odds": "1.909",
"header": "Under",
"name": "10"
}
]
},
"total_corners": {
"id": "3",
"name": "Total Corners",
"odds": [
{
"id": "204",
"odds": "17.000",
"name": "Under 6"
},
{
"id": "205",
"odds": "4.333",
"name": "6 - 8"
},
{
"id": "206",
"odds": "2.750",
"name": "9 - 11"
},
{
"id": "207",
"odds": "3.400",
"name": "12 - 14"
},
{
"id": "208",
"odds": "5.500",
"name": "Over 14"
}
]
}
}
}
}
]
}
Josson Query
results.map(
FI,
event_id,
categories: entries().[value.isObject()]*
.map(category: key,
value.updated_at,
value.key,
details: value.sp.**
.map(market_id: id,
market: name,
odds)
.unwind(odds)
).unwind(details)
).unwind(categories)
Output
[ {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "cards",
"updated_at" : "1660559432",
"key" : "AAA100",
"market_id" : "1",
"market" : "Cards",
"id" : "101",
"odds" : "2.200",
"header" : "Over",
"name" : "11"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "cards",
"updated_at" : "1660559432",
"key" : "AAA100",
"market_id" : "1",
"market" : "Cards",
"id" : "102",
"odds" : "8.500",
"header" : "Exactly",
"name" : "11"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "cards",
"updated_at" : "1660559432",
"key" : "AAA100",
"market_id" : "1",
"market" : "Cards",
"id" : "103",
"odds" : "1.909",
"header" : "Under",
"name" : "11"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "corners",
"updated_at" : "1660559431",
"key" : "AAA200",
"market_id" : "2",
"market" : "Corners",
"id" : "201",
"odds" : "2.200",
"header" : "Over",
"name" : "10"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "corners",
"updated_at" : "1660559431",
"key" : "AAA200",
"market_id" : "2",
"market" : "Corners",
"id" : "202",
"odds" : "8.500",
"header" : "Exactly",
"name" : "10"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "corners",
"updated_at" : "1660559431",
"key" : "AAA200",
"market_id" : "2",
"market" : "Corners",
"id" : "203",
"odds" : "1.909",
"header" : "Under",
"name" : "10"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "corners",
"updated_at" : "1660559431",
"key" : "AAA200",
"market_id" : "3",
"market" : "Total Corners",
"id" : "204",
"odds" : "17.000",
"name" : "Under 6"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "corners",
"updated_at" : "1660559431",
"key" : "AAA200",
"market_id" : "3",
"market" : "Total Corners",
"id" : "205",
"odds" : "4.333",
"name" : "6 - 8"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "corners",
"updated_at" : "1660559431",
"key" : "AAA200",
"market_id" : "3",
"market" : "Total Corners",
"id" : "206",
"odds" : "2.750",
"name" : "9 - 11"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "corners",
"updated_at" : "1660559431",
"key" : "AAA200",
"market_id" : "3",
"market" : "Total Corners",
"id" : "207",
"odds" : "3.400",
"name" : "12 - 14"
}, {
"FI" : "120986750",
"event_id" : "5164306",
"category" : "corners",
"updated_at" : "1660559431",
"key" : "AAA200",
"market_id" : "3",
"market" : "Total Corners",
"id" : "208",
"odds" : "5.500",
"name" : "Over 14"
} ]
(73025233)
Dataset api1
{
"name": "name1",
"address": "",
"skillset": [
{
"lang": "java",
"projectName": "project1"
},
{
"lang": "c++",
"projectName": "project2"
}
]
}
Dataset api2
{
"name": "name1",
"address": "",
"skillset": [
{
"lang": "c++",
"projectName": "project2"
},
{
"lang": "java",
"projectName": "project1"
}
]
}
Jossons Query
api1 = api2
Output
true
(73674131)
{
"idno":6473853,
"user":"GCA_GB",
"operation":"U",
"timestamp":"2022-08-22T13:14:48",
"first_name":{
"old":"rak",
"new":"raki"
},
"fam_name":{
"old":"gow",
"new":"gowda"
}
}
Josson Query
map(idno, user, operation, timestamp,
changes: entries().[value.isObject()]*.map(key::value))
.unwind(changes)
Output
[ {
"idno" : 6473853,
"user" : "GCA_GB",
"operation" : "U",
"timestamp" : "2022-08-22T13:14:48",
"first_name" : {
"old" : "rak",
"new" : "raki"
}
}, {
"idno" : 6473853,
"user" : "GCA_GB",
"operation" : "U",
"timestamp" : "2022-08-22T13:14:48",
"fam_name" : {
"old" : "gow",
"new" : "gowda"
}
} ]
(73456238)
{
"test": "t",
"testInt": 1,
"b": {
"testDouble": 1.1,
"c": [{
"testFloat": 1.2
}]
}
}
Josson Query
flatten('_')
Output flattened
{
"test" : "t",
"testInt" : 1,
"b_testDouble" : 1.1,
"b_c_0_testFloat" : 1.2
}
Josson Query
unflatten('_')
Output unflatten
{
"test" : "t",
"testInt" : 1,
"b" : {
"testDouble" : 1.1,
"c" : [ {
"testFloat" : 1.2
} ]
}
}
(73598200)
{
"id": 11,
"item": [
{
"id": "11_1",
"action": "add",
"type": {
"id": "11_1_xx",
"typeName": "xx"
},
"item": [
{
"id": "11_1_1",
"action": "add",
"type": {
"id": "11_1_1_zz",
"typeName": "zz"
},
"item": [
{
"id": "11_1_1_1",
"action": "add",
"type": {
"id": "11_1_1_1_xx",
"typeName": "xx"
}
}
]
},
{
"id": "11_1_2",
"action": "add",
"type": {
"id": "11_1_2_xx",
"typeName": "xx"
},
"item": [
{
"id": "11_1_2_1",
"action": "add",
"type": {
"id": "11_1_2_1_zz",
"typeName": "zz"
}
}
]
}
]
}
]
}
Josson Query
cumulateCollect(item[type.typeName='xx']*.field(item:), item).flatten(1)
Output
[ {
"id" : "11_1",
"action" : "add",
"type" : {
"id" : "11_1_xx",
"typeName" : "xx"
}
}, {
"id" : "11_1_2",
"action" : "add",
"type" : {
"id" : "11_1_2_xx",
"typeName" : "xx"
}
}, {
"id" : "11_1_1_1",
"action" : "add",
"type" : {
"id" : "11_1_1_1_xx",
"typeName" : "xx"
}
} ]
(72426983)
{
"userId": "1",
"age": "20",
"firstName": "firstname1",
"lastname": "lastname1",
"zipCode": "zipcode1",
"street": "street1",
"city": "city1",
"country": "country",
"gender": "gender1",
"grade": "grade1",
"birthday": "birthday1"
}
Josson Query
map(ID:userId, age, firstName, lastname,
address:collect(
map(code:'custom-field1',value:zipCode),
map(code:'custom-field2',value:street),
map(code:'custom-field3',value:city),
map(code:'custom-field4',value:country)),
gender, grade, birthday)
Output
{
"ID" : "1",
"age" : "20",
"firstName" : "firstname1",
"lastname" : "lastname1",
"address" : [ {
"code" : "custom-field1",
"value" : "zipcode1"
}, {
"code" : "custom-field2",
"value" : "street1"
}, {
"code" : "custom-field3",
"value" : "city1"
}, {
"code" : "custom-field4",
"value" : "country"
} ],
"gender" : "gender1",
"grade" : "grade1",
"birthday" : "birthday1"
}
(72701610)
{
"treasure": [
{
"aname": "FOO",
"bname": "BAR"
}
]
}
Josson Query
treasure
.entries()
.map(if([key='aname'], if([value='FOO'],'fname',key), 'sname')::value)
.mergeObjects()
Output
{
"fname" : "FOO",
"sname" : "BAR"
}
(73945727)
{
"status": [
{
"id": "online",
"state": "valid"
},
{
"id": "busy",
"state": "unknown"
},
{
"id": "any",
"state": "unknow",
"moreInfo": "unavailable"
}
],
"users": [
{
"title": "foo",
"availability": [
"online",
"busy"
]
},
{
"title": "bar",
"availability": [
"busy",
"any"
]
},
{
"title": "baz",
"availability": [
"any"
]
}
]
}
Josson Query
map(users.field([email protected]($id:?).get($.status[id=$id])))
Output
{
"users" : [ {
"title" : "foo",
"availability" : [ {
"id" : "online",
"state" : "valid"
}, {
"id" : "busy",
"state" : "unknown"
} ]
}, {
"title" : "bar",
"availability" : [ {
"id" : "busy",
"state" : "unknown"
}, {
"id" : "any",
"state" : "unknow",
"moreInfo" : "unavailable"
} ]
}, {
"title" : "baz",
"availability" : [ {
"id" : "any",
"state" : "unknow",
"moreInfo" : "unavailable"
} ]
} ]
}
(73616066)
[
{
"names": "Car",
"values": "Toyota"
},
{
"names": "Bike",
"values": "Schwinn"
},
{
"names": "Scooter",
"values": "Razor"
},
{
"names": "A0",
"values": "11"
},
{
"names": "A1",
"values": "12"
},
{
"names": "A2",
"values": "13"
},
{
"names": "B0",
"values": "2000"
},
{
"names": "B1",
"values": "4000"
},
{
"names": "B2",
"values": "22000"
}
]
Josson Query
@collect([names !=~ '\D\d+']*
.map(names::values)
,[names =~ '\D\d+']*
.group(names.substr(1), map(names::values))@
.elements
.mergeObjects()
.@toObject('Data')
)
.flatten(1)
.mergeObjects()
Output
{
"Car" : "Toyota",
"Bike" : "Schwinn",
"Scooter" : "Razor",
"Data" : [ {
"A0" : "11",
"B0" : "2000"
}, {
"A1" : "12",
"B1" : "4000"
}, {
"A2" : "13",
"B2" : "22000"
} ]
}
(72272928)
Dataset resp1
{
"data": {
"values": {
"name": "kiran",
"age": "24"
}
}
}
Dataset resp2
{
"data": {
"values": {
"name": "Mamu",
"age": "26"
}
}
}
Jossons Query
resp1->data.toArray() <+< resp2->data.toArray()
Output mergeResult
[ {
"name" : "kiran",
"age" : "24"
}, {
"name" : "Mamu",
"age" : "26"
} ]
Josson Query
toObject('values').toObject('data')
Output
{
"data" : {
"values" : [ {
"name" : "kiran",
"age" : "24"
}, {
"name" : "Mamu",
"age" : "26"
} ]
}
}
(47427518)
[
{
"router_id": "1101",
"floor_id": "20",
"building_id": "2",
"router_name": "1101"
},
{
"router_id": "1102",
"floor_id": "20",
"building_id": "2",
"router_name": "1102"
},
{
"router_id": "0",
"floor_id": "20",
"building_id": "2",
"router_name": "pancoordinator"
},
{
"router_id": "1104",
"floor_id": "20",
"building_id": "2",
"router_name": "1104"
}
]
Josson Query
group(building_id).map(
building_id,
floors: elements.group(floor_id).map(
floor_id,
routers: elements.map(
router_id, router_name)
)
)
.toObject('buildings')
Output
{
"buildings" : [ {
"building_id" : "2",
"floors" : [ {
"floor_id" : "20",
"routers" : [ {
"router_id" : "1101",
"router_name" : "1101"
}, {
"router_id" : "1102",
"router_name" : "1102"
}, {
"router_id" : "0",
"router_name" : "pancoordinator"
}, {
"router_id" : "1104",
"router_name" : "1104"
} ]
} ]
} ]
}
(51576987)
{
"values": [
{
"locale": "en_US",
"source_key": "book_format",
"value": "Hardback",
"display_attr_name": "Book Format",
"source_value": "Hardback",
"isPrimary": "true"
},
{
"isFacetValue": "true",
"facet_version": "1.1",
"locale": "en_US",
"value": "Hardcover"
}
]
}
Josson Query
values.mergeObjects()
Output
{
"locale" : "en_US",
"source_key" : "book_format",
"value" : "Hardcover",
"display_attr_name" : "Book Format",
"source_value" : "Hardback",
"isPrimary" : "true",
"isFacetValue" : "true",
"facet_version" : "1.1"
}
(73506183)
[ {
"count" : 15,
"_id" : {
"DB User Name" : "admin",
"Session Activity Type" : "LOGOFF"
}
}, {
"count" : 16,
"_id" : {
"DB User Name" : "dbuser",
"Session Activity Type" : "LOGON"
}
}, {
"count" : 17,
"_id" : {
"DB User Name" : "dbuser",
"Session Activity Type" : "LOGOFF"
}
}, {
"count" : 18,
"_id" : {
"DB User Name" : "admin",
"Session Activity Type" : "LOGON"
}
} ]
Josson Query
field(_id.field(DB User Name.if(equals('dbuser'),'updated-Admin',?)))
Output
[ {
"count" : 15,
"_id" : {
"DB User Name" : "admin",
"Session Activity Type" : "LOGOFF"
}
}, {
"count" : 16,
"_id" : {
"DB User Name" : "updated-Admin",
"Session Activity Type" : "LOGON"
}
}, {
"count" : 17,
"_id" : {
"DB User Name" : "updated-Admin",
"Session Activity Type" : "LOGOFF"
}
}, {
"count" : 18,
"_id" : {
"DB User Name" : "admin",
"Session Activity Type" : "LOGON"
}
} ]
(72790475)
[
{
"ID": "id1",
"ref": "ref1",
"categ": "CATEG_A",
"pagenb": 1
},
{
"ID": "id2",
"ref": "ref1",
"categ": "CATEG_A",
"pagenb": 2
},
{
"ID": "id3",
"ref": "ref1",
"categ": "CATEG_B",
"pagenb": 3
}
]
Josson Query
group(map(ref, categ), ID).map(ID, key.ref, key.categ)
Output
[ {
"ID" : [ "id1", "id2" ],
"ref" : "ref1",
"categ" : "CATEG_A"
}, {
"ID" : [ "id3" ],
"ref" : "ref1",
"categ" : "CATEG_B"
} ]
(73405994)
{
"cnpj": {
"numeroCNPJ": "string"
},
"codigoCNES": {
"codigo": "string"
},
"codigoUnidade": {
"codigo": "string"
},
"diretor": {
"cpf": {
"numeroCPF": "string"
},
"nome": {
"nome": "string"
}
},
"nomeEmpresarial": {
"nome": "string"
},
"nomeFantasia": {
"nome": "string"
}
}
Josson Query
entries().map(
key::value.if([size()=1], *, **.mergeObjects())
)
Output
{
"cnpj" : "string",
"codigoCNES" : "string",
"codigoUnidade" : "string",
"diretor" : {
"numeroCPF" : "string",
"nome" : "string"
},
"nomeEmpresarial" : "string",
"nomeFantasia" : "string"
}
(35438323)
[
{
"name": "Team Wolf",
"www": "http://www.teamwolf.qqq",
"department": "department1",
"team1": "team1"
},
{
"name": "Team Fox",
"www": "http://www.teamfox.qqq",
"department": "department1",
"team2": "team2"
},
{
"name": "Team Falcon",
"www": "http://www.teamfalcon.qqq",
"department": "department1",
"team3": "team3"
}
]
Josson Query
group(department).map(
department:: elements.map(
~'^team.*':: map(
name, www
)
)
.mergeObjects()
)
Output
[ {
"department1" : {
"team1" : {
"name" : "Team Wolf",
"www" : "http://www.teamwolf.qqq"
},
"team2" : {
"name" : "Team Fox",
"www" : "http://www.teamfox.qqq"
},
"team3" : {
"name" : "Team Falcon",
"www" : "http://www.teamfalcon.qqq"
}
}
} ]
(73397763)
{
"errorCount": 2,
"errorIndices": [
0,
1
],
"data": [
{
"errorCode": 901,
"errorMessage": "IBad data: Check the data",
"errorData": "xxxx"
},
{
"errorCode": 901,
"errorMessage": "IBad data: Check the data",
"errorData": "XZY"
},
"fun now"
]
}
Josson Query
field(data[isText()]*, errors: data[isObject()]*)
Output
{
"errorCount" : 2,
"errorIndices" : [ 0, 1 ],
"data" : [ "fun now" ],
"errors" : [ {
"errorCode" : 901,
"errorMessage" : "IBad data: Check the data",
"errorData" : "xxxx"
}, {
"errorCode" : 901,
"errorMessage" : "IBad data: Check the data",
"errorData" : "XZY"
} ]
}
(73224582)
{
"header": [
{
"key": "numberOfRecords",
"value": "122",
"valueDataType": "string"
},
{
"key": "g_udit",
"value": "1",
"valueDataType": "string"
},
{
"key": "userNameId",
"value": "155",
"valueDataType": "string"
}
]
}
Josson Query
map(header.map(key::value).mergeObjects())
Output
{
"header" : {
"numberOfRecords" : "122",
"g_udit" : "1",
"userNameId" : "155"
}
}
(73200231)
{
"data": {
"myProp": true,
"myAnother": true,
"myAnotherOne": false
}
}
Josson Query
map(values: data.entries().[value]*.key.upperSnakeCase())
Output
{
"values" : [ "MY_PROP", "MY_ANOTHER" ]
}
(73190751)
{
"data": [
{
"fieldname": "Name",
"fieldvalue": [
"John Doe"
]
},
{
"fieldname": "Title",
"fieldvalue": [
"Manager"
]
},
{
"fieldname": "Company",
"fieldvalue": [
"Walmart"
]
}
]
}
Josson Query
map(
finalPayload: map(
PI: map(
EmpName: data[fieldname='Name'].fieldvalue[0],
EmpRole: data[fieldname='Title'].fieldvalue[0]
),
Company: data[fieldname='Company'].fieldvalue[0]
)
)
Output
{
"finalPayload" : {
"PI" : {
"EmpName" : "JohnDoe",
"EmpRole" : "Manager"
},
"Company" : "Walmart"
}
}
(73131799)
Dataset left
{
"package": {
"institution": [
{
"school": "TP"
}
],
"people": [
{
"age": 32,
"name": "Bob"
},
{
"age": 16,
"name": "Amanda"
}
],
"details": [
{
"course": "Humanities",
"duration": 4,
"description": "Students in Computer Sci"
}
]
}
}
Dataset right
{
"package": {
"institution": [
{
"school": "MIT"
}
],
"people": [
{
"age": 32,
"name": "Bob"
},
{
"age": 16,
"name": "Samantha"
},
{
"age": 20,
"name": "Dylan"
}
],
"details": [
{
"course": "Computer Sci",
"duration": 4,
"description": "Students in Computer Sci"
}
]
}
}
Jossons Query
left->package >-> right->package
Output
{
"institution" : [ {
"school" : "MIT"
} ],
"people" : [ {
"age" : 16,
"name" : "Samantha"
}, {
"age" : 20,
"name" : "Dylan"
} ],
"details" : [ {
"course" : "Computer Sci",
"duration" : 4,
"description" : "Students in Computer Sci"
} ]
}
(72442001)
{
"new": {
"bc_sku_partner": [
"Amazon",
"Ebay"
],
"bc_sku_channel": [
"Partner",
"Online",
"Store"
],
"cc_sku_channel": [
"Store"
]
}
}
Josson Query
new
.entries()
.unwind(value)
.[key='bc_sku_partner' | value!='Partner']*
.map(catalog:key.substr(0,2).upperCase(),
channel:if([key='bc_sku_partner'],'Partner',value),
partner:if([key='bc_sku_partner'],value))
.toObject('catalogs')
Output
{
"catalogs" : [ {
"catalog" : "BC",
"channel" : "Partner",
"partner" : "Amazon"
}, {
"catalog" : "BC",
"channel" : "Partner",
"partner" : "Ebay"
}, {
"catalog" : "BC",
"channel" : "Online"
}, {
"catalog" : "BC",
"channel" : "Store"
}, {
"catalog" : "CC",
"channel" : "Store"
} ]
}
(72442443)
{
"id": "1234",
"recordType": "E",
"receiveDate": "2009-01-01",
"receiveTime": "09:55:00",
"releaseDate": "2009-01-02",
"releaseTime": "08:30:00",
"classifications": [
{
"reportType": 1031435,
"description": {
"string": "Progress Report"
}
},
{
"reportType": 8888888,
"description": {
"string": "Net Tangible Asset Backing"
}
}
],
"type": "ASX"
}
Josson Query
map(id,
recordType,
classifications
.map(reportType, description:description.string)
.wrap()
.collect([0],
ifNot([reportType=8888888], json('{"reportType":8888888,"description":"Default item"}')),
ifNot([reportType=9999999], json('{"reportType":9999999,"description":"Default item"}')))
.flatten(1),
type)
Output
{
"id" : "1234",
"recordType" : "E",
"classifications" : [ {
"reportType" : 1031435,
"description" : "Progress Report"
}, {
"reportType" : 8888888,
"description" : "Net Tangible Asset Backing"
}, {
"reportType" : 9999999,
"description" : "Default item"
} ],
"type" : "ASX"
}
(70753154)
{
\"group\": [
{
\"schema\": \"file\"
},
{
\"key1\": \"val1\",
\"key2\": \"val2\"
},
{
\"schema\": \"folder\"
},
{
\"key1\": \"val1\",
\"key2\": \"val2\",
\"key3\": \"val3\"
},
{
\"schema\": \"dir\"
},
{
\"key1\": \"val1\",
\"key2\": \"val2\",
\"key3\": \"val3\",
\"key4\": \"val4\"
}
]
}
Josson Query
group
.group(#.floor(calc(?/2)))@
.mergeObjects(elements)
.@toObject('group')
Output
{
"group" : [ {
"schema" : "file",
"key1" : "val1",
"key2" : "val2"
}, {
"schema" : "folder",
"key1" : "val1",
"key2" : "val2",
"key3" : "val3"
}, {
"schema" : "dir",
"key1" : "val1",
"key2" : "val2",
"key3" : "val3",
"key4" : "val4"
} ]
}
(54502431)
{
"entry": [
{
"resource": {
"id": "car-1",
"type": "vehicle",
"color": "red",
"owner": {
"ref": "Person/person-1"
}
}
},
{
"resource": {
"id": "car-2",
"type": "vehicle",
"color": "blue",
"owner": {
"ref": "Person/person-2"
}
}
},
{
"resource": {
"id": "person-1",
"type": "Person",
"name": "john"
}
},
{
"resource": {
"id": "person-2",
"type": "Person",
"name": "wick"
}
}
]
}
Josson Query
entry
.resource
.group(if([type='vehicle'], owner.ref.removeStart('Person/'), id),
if([type='vehicle'], field(owner:), map(ownername:name)))@
.elements
.mergeObjects()
Output
[ {
"id" : "car-1",
"type" : "vehicle",
"color" : "red",
"ownername" : "john"
}, {
"id" : "car-2",
"type" : "vehicle",
"color" : "blue",
"ownername" : "wick"
} ]
(73884462)
[
{
"coupon": "VAR",
"currency": "USD",
"sip": "94989WAX5",
"lastModifiedDate": "2022-09-23T08:16:25Z"
},
{
"coupon": "VAR1",
"currency": "USD",
"sip": "94989WAX5",
"lastModifiedDate": "2022-09-21T08:16:25Z"
},
{
"coupon": "VAR3",
"currency": "USD",
"sip": "XHBRYWEB1",
"lastModifiedDate": "2022-09-20T08:16:25Z"
}
]
Josson Query
group(sip)@.elements.findByMax(lastModifiedDate)
Output
[ {
"coupon" : "VAR",
"currency" : "USD",
"sip" : "94989WAX5",
"lastModifiedDate" : "2022-09-23T08:16:25Z"
}, {
"coupon" : "VAR3",
"currency" : "USD",
"sip" : "XHBRYWEB1",
"lastModifiedDate" : "2022-09-20T08:16:25Z"
} ]