Popularity
1.9
Growing
Activity
8.0
Declining
14
3
1

Description

Josson is a complete query and transformation language for JSON.

- Query a JSON dataset. - Restructure JSON data. - Has many functions to format text output. - Has many functions to manipulate date values. - Has many functions to work on array node. - Can be used as an API parameter to trim down the response JSON result.

Jossons is a template engine to generate text output.

- Query data from multiple JSON datasets. - Join two JSON datasets to build a new dataset. - Resolve template placeholder from external data source on demand. - I used Jossons to generate millions of SMS/Email notifications during the first year. - I used Jossons to generate reports that retrieve data from MongoDB directly without writing a line of code. - Can be used to build a rule engine.

Programming language: Java
License: Apache License 2.0
Tags: JSON     JSON Processing     Template Engine     Java     Projects    

Josson & Jossons alternatives and similar libraries

Based on the "JSON" category.
Alternatively, view Josson & Jossons alternatives based on common mentions on social networks and blogs.

Do you think we are missing an alternative of Josson & Jossons or a related project?

Add another 'JSON' Library

README

Josson & Jossons

  • Josson is a query language for JSON.
  • Jossons is a template engine to generate text output.

Features and Capabilities of Josson

  • Query data from a JSON.
  • Restructure a JSON.
  • Has many functions to format text output.
  • Has many functions to manipulate date values.
  • Has many functions to work on array node.
  • Can be used as an API parameter to trim down the response JSON result.

Features and Capabilities of Jossons

  • Query data from multiple JSON datasets.
  • Join two JSON datasets to build a new dataset.
  • Resolve template placeholder from external data source on demand.
  • I used Jossons to generate millions of SMS/Email notifications during the first year.
  • I used Jossons to generate reports that retrieve data from MongoDB directly without writing a line of code.

Table of Contents

  • Josson Basic
  • Josson Path Chart Elements
  • Josson Query Language
  • Josson Functions
    • Arithmetic Functions
    • String Functions
    • Date Functions
    • Format Functions
    • Logical Functions
    • Array Functions
    • Structural Functions
  • Jossons Basic
  • Jossons Template Language
    • Ternary Syntax
    • Implicit Variables
    • Fill In
  • Jossons Resolver
    • Dictionary Finder
    • Data Finder
    • Join Datasets
  • Appendix
    • MongoDB Adapter

Josson Basic

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);

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(jossonPath);

Josson Path Chart Elements

Josson path chart shows data type changes and data flow along the path. Data filtering, transformation and formatting details are not included.

     %              Any JSON node

     [%]            Array node

     {}             Object node

     ""             Text node

     $I             Integer node

     $D             Double node

     $TF            Boolean node

     null           Null node

     $V             Any value node ""|$I|$D|$TF|null

     $V...          Multiple value nodes inside function arguments

     ->             A path step progress

     obj{}          A named object node

     {}.%           Object parent-child relation is connected by a "." (Except the branch's 1st step)

   {}[]->{}         An object with validation filter

   [%]*->[%]        An array node proceeded one step with all its elements

   [%][]->%         An array node with find-first filter

   [%][]*->[%]      An array node with find-all filter

          %->
         /          An array node with find-all filter and
   [%][]@           divert each filtered element to separate branch
         \
          %->

   ->func()->       A function that manipulate the current node

 ->[%->func()]->    A function that manipulate each element of an array node

    func(?)         Function parameter symbol "?" represents the current node

   .--->---.
  /         \       Function parameter symbol "@" represents the parent array node
[]->[%->func(@)]

   [[%]]->[%]       Nested array is flattened each step

        %->
       /
  ->[]@             Divert each array element to separate branch
       \
        %->
  ->%
     \
      @->[%]        Merge branches into an array
     /
  ->%

     ==>%           Final result

  ->%
     \
      ==>[%]        Merge branches to a final result
     /
  ->%

      !!            The position where the step is unresolvable 

==>!unresolvable!   Unable to resolve the result (Returns a Java null value)

Josson Query Language

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
}
  1. To query a value node.

    josson.getNode("salesPerson")
    ==>
    "Raymond"
    

Path chart

    {}->salesPerson ==>""
  1. Node name is case-sensitive. Josson returns null value if the path is unresolvable.

    josson.getNode("salesperson")
    ==>
    !unresolvable!
    

Path chart

    {}->salesperson!! ==>!unresolvable!
  1. To query an object node.

    josson.getNode("customer")
    ==>
    {
        "customerId" : "CU0001",
        "name" : "Peggy",
        "phone" : "+852 62000610"
    }
    

Path chart

    {}->customer{} ==>{}
  1. Object parent-child relation is connected by a ..

    josson.getNode("customer.name")
    ==>
    "Peggy"
    

Path chart

    {}->customer.name ==>""
  1. 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() ==>""
  1. Function name is case-insensitive.
    A path argument takes the function's current node as its parent.

    josson.getNode("customer.UPPERCase(name)")
    ==>
    "PEGGY"
    

Path chart

    {}->customer{}->UPPERCase($V) ==>""
  1. If the function is the first path step, it works on the root node.

    josson.getNode("upperCase(customer.name)")
    ==>
    "PEGGY"
    

Path chart

    {}->upperCase($V) ==>""
  1. Functions can be nested and the parameters have the same parent node.

    josson.getNode("customer.concat(upperCase(name), ' / ', phone)")
    ==>
    "PEGGY / +852 62000610"
    

Path chart

    {}->customer{}->concat($V...) ==>""
  1. A path start with numbers override the data and produces an integer node.

    josson.getNode("123")
    ==>
    123
    

Path chart

    $I ==>$I
  1. A path start with numbers and has . produces a double node.

    josson.getNode("123.40")
    ==>
    123.4
    

    Path chart

    $D ==>$D
    
  2. 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

    "" ==>""
    
  3. 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
    
  4. 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* ==>[{}]
    
  5. An array filter is enclosed by square brackets.
    To query an array element by 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[0] ==>{}
    
  6. To query a value node in an array element.

    josson.getNode("items[1].name")
    ==>
    "OctoPlus Tennis Racket - Star"
    

    Path chart

    {}->items[1].name ==>""
    
  7. To query an object node in an array element.

    josson.getNode("items[2].property")
    ==>
    {
      "size" : "35",
      "colors" : [ "RED" ]
    }
    

    Path chart

    {}->items[2].property{} ==>{}
    
  8. 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]
    
  9. 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($V)] ==>[""]
    
  10. For function argument, a path step ? represents the current node.

    josson.getNode("items.qty.concat('Qty=',?)")
    ==>
    [ "Qty=2", "Qty=1", "Qty=1" ]
    

    Path chart

    {}->items*->[qty]->[$I->concat(?)] ==>[""]
    
  11. A function that manipulates an array node and produce a value node.

    josson.getNode("items.qty.sum()")
    ==>
    4.0
    

    Path chart

    {}->items*->[qty]->sum() ==>$D
    
  12. Uses Java standard formatting pattern.

    josson.getNode("items.sum(qty).formatNumber('#,##0')")
    ==>
    "4"
    

    Path chart

    {}->items*->[{}]->sum([$V])->formatNumber() ==>""
    
  13. Find the first matching element by array filter.

    josson.getNode("items.itemCode[!startsWith('A')]")
    ==>
    "B00001"
    

    Path chart

    {}->items*->[itemCode][] ==>""
    
  14. Filter using relational operators =, !=, >, >=, < and <=.

    josson.getNode("items[unitDiscount > 0].name")
    ==>
    "OctoPlus Tennis Racket - Star"
    

    Path chart

    {}->items[].name ==>""
    
  15. Returns null value if nothing matches the array filter.

    josson.getNode("items[unitDiscount > 100].name")
    ==>
    !unresolvable!
    

    Path chart

    {}->items[]!!.name ==>!unresolvable!
    
  16. 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] ==>[""]
    
  17. For each path step, a nested array is flattened once.

    josson.getNode("items[true]*.tags[true]*")
    ==>
    [ "SHIRT", "WOMEN", "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
    

    Path chart

    {}->items[]*->[tags[]*->[""]] ==>[""]
    
  18. 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*->[""]] ==>[""]
    
  19. 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] ==>[""]
    
  20. 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*->[""]] ==>[""]
    
  21. 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*->[""]
    
  22. Some 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() ==>""
    
  23. 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()->""
              /                           \
    {}->[email protected]                             ==>[""]
              \                           /
               {}->tags*->[""]->join()->""
    
  24. 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()->""
    
  25. Modifier @ before a function name merges all branch results into a single array before manipulation.

    josson.getNode("[email protected]('+')[email protected](' / ')")
    ==>
    "SHIRT+WOMEN / TENNIS+SPORT+RACKET / SHOE+SPORT+WOMEN"
    

    Path chart

               {}->tags*->[""]->join()->""
              /                           \
    {}->[email protected]                             @->[""]->join() ==>""
              \                           /
               {}->tags*->[""]->join()->""
    
  26. Syntax []@ can divert the array output of function.

    josson.getNode("'1+2 | 3+4 | 5+6'.split('|').[]@.split('+').calc(?*2).round(0).join('+').concat('(',?,')/2')[email protected](' | ')")
    ==>
    "(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()
    
  27. Function parameter can be a value node of parent.

    josson.getNode("[email protected](concat('[',brand,'] ',name,'\n'), qty)[email protected]()")
    ==>
    "[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($V...)->""
              /                     \
    {}->[email protected]                       @->[""]->join()==>""
              \                     /
               {}->repeat($V...)->""
    
  28. Functions work on array and produce an array, such as concat(), manipulate on each element.
    An argument # denotes the zero-based array index.

    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(#, $V...)]->join() ==>""
    
  29. An argument ## denotes the one-based array index.
    A function argument path step start with @ represents the parent array node.

    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($V)->[{}->concat(@, ##, $V...)]->join() ==>""
    
  30. An object node with a validation filter.

    josson.getNode("customer[name='Peggy']")
    ==>
    {
      "customerId" : "CU0001",
      "name" : "Peggy",
      "phone" : "+852 62000610"
    }
    

    Path chart

    {}->customer[] ==>{}
    
  31. An object node with a validation filter.

    josson.getNode("customer[name='Raymond']")
    ==>
    !unresolvable!
    

    Path chart

    {}->customer[]!! ==>!unresolvable!
    
  32. Function json() parse a JSON string.

    josson.getNode("json('[1,2,"3"]')")
    ==>
    [ 1, 2, "3" ]
    

    Path chart

    [] ==>[]
    
  33. Relational operator = and != support object comparison.

    josson.getNode("[customer = json('{"name":"Peggy","phone":"+852 62000610","customerId":"CU0001"}')].isNotNull()")
    ==>
    true
    

    Path chart

    {}->{}[]->{}->isNotNull() ==>$TF
    
  34. 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
    
  35. 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($V...)]->[$D->concat(?, ##)] ==>[""]
    
  36. Non-array manipulate functions preserve null element.

    josson.getNode("items.calc(qty * (unitPrice-unitDiscount)).[##<=2]*.concat(##,'=',?)")
    ==>
    [ null, "2=140.0" ]
    

    Path chart

    {}->items*->[{}->calc($V...)]->[$D][]*->[$D->concat(?, ##)] ==>[""]
    
  37. 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($V...)]->[$D->concat(?, ##)]->join() ==>""
    
  38. 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($V...)]->[$D][]*->[$D->concat(?, ##)] ==>[""]
    
  39. 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($V...)]->[$D][]*->[$D->concat(?, #A)]->join() ==>""
    
  40. Merge Diverted branches throws away null nodes automatically. An argument #a denotes the lowercase alphabetic array index.

    josson.getNode("[email protected](qty * (unitPrice-unitDiscount))[email protected](#a,'=',?)")
    ==>
    [ "a=140.0", "b=100.0" ]
    

    Path chart

               {}->calc($V...)->$D
              /                   \
    {}->[email protected]                     @->[$D->concat(?, #a)] ==>[""]
              \                   /
               {}->calc($V...)->$D
    
  41. mXparser expression accepts single-level path only. To apply multi-level 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($V...)]->[$D->formatNumber()] ==>[""]
    
  42. 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(?)] ==>[""]
    
  43. 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[0]->{}->entries() ==>[{}]
    
  44. Function keys() lists an object's key names.

    josson.getNode("keys()")
    ==>
    [ "salesOrderId", "salesDate", "salesPerson", "customer", "items", "totalAmount" ]
    

    Path chart

    {}->keys() ==>[""]
    
  45. 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(?) ==>[""]
    
  46. Function toArray() puts an object's values into an array.

    josson.getNode("customer.toArray()")
    ==>
    [ "CU0001", "Peggy", "+852 62000610" ]
    

    Path chart

    {}->customer->toArray() ==>[""]
    
  47. 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

    {}->customer->toArray($V...) ==>[""]
    
  48. Function map() constructs a new object node. For multi-level path, the last element name will become the new element name. To rename an element, use syntax newFieldName: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($V...) ==>{}
    
  49. Function field() adds, removes and renames field on the current object node. To remove an element, use syntax fieldName:.

    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($V...) ==>{}
    
  50. 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($V...)] ==>[{}]
    
  51. Function flatten() flatten an array same as the default path step behavior. But more readable.

    josson.getNode("[email protected]")
    ==>
    [ [ "SHIRT", "WOMEN" ], [ "TENNIS", "SPORT", "RACKET" ], [ "SHOE", "SPORT", "WOMEN" ] ]
    
    josson.getNode("[email protected]@flatten()")
    ==>
    [ "SHIRT", "WOMEN", "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
    
    josson.getNode("[email protected]@[true]*")
    ==>
    [ "SHIRT", "WOMEN", "TENNIS", "SPORT", "RACKET", "SHOE", "SPORT", "WOMEN" ]
    

    Path chart

               {}->tags*->[""]
              /               \
    {}->[email protected]                 @->[[""]]->flatten() ==>[""]
              \               /
               {}->tags*->[""]
    

Josson Functions

There are over 180 functions. They are classified into categories:

Arithmetic Functions

  1. abs()
  2. calc()
  3. ceil()
  4. floor()
  5. mod()
  6. round()

String Functions

  1. abbreviate()
  2. appendIfMissing()
  3. appendIfMissingIgnoreCase()
  4. capitalize()
  5. center()
  6. concat()
  7. keepAfter()
  8. keepAfterIgnoreCase()
  9. keepAfterLast()
  10. keepAfterLastIgnoreCase()
  11. keepBefore()
  12. keepBeforeIgnoreCase()
  13. keepBeforeLast()
  14. keepBeforeLastIgnoreCase()
  15. leftPad()
  16. length()
  17. lowerCase()
  18. notEmpty()
  19. notBlank()
  20. prependIfMissing()
  21. prependIfMissingIgnoreCase()
  22. removeEnd()
  23. removeEndIgnoreCase()
  24. removeStart()
  25. removeStartIgnoreCase()
  26. repeat()
  27. replace()
  28. replaceIgnoreCase()
  29. rightPad()
  30. split()
  31. strip()
  32. stripEnd()
  33. stripStart()
  34. substr()
  35. trim()
  36. uncapitalize()
  37. upperCase()

Date Functions

  1. amPmOfDay()
  2. second()
  3. secondOfDay()
  4. minute()
  5. minuteOfDay()
  6. hourOfAmPm()
  7. hour()
  8. dayOfWeek()
  9. day()
  10. dayOfYear()
  11. month()
  12. year()
  13. plusSeconds()
  14. plusMinutes()
  15. plusHours()
  16. plusDays()
  17. plusWeeks()
  18. plusMonths()
  19. plusYears()
  20. minusSeconds()
  21. minusMinutes()
  22. minusHours()
  23. minusDays()
  24. minusWeeks()
  25. minusMonths()
  26. minusYears()
  27. truncateToMicro()
  28. truncateToMilli()
  29. truncateToSecond()
  30. truncateToMinute()
  31. truncateToHour()
  32. truncateToDay()
  33. truncateToMonth()
  34. truncateToYear()
  35. withNano()
  36. withMicro()
  37. withMilli()
  38. withSecond()
  39. withMinute()
  40. withHour()
  41. withDay()
  42. withDayOfYear()
  43. withMonth()
  44. withYear()
  45. dayEnd()
  46. monthEnd()
  47. yearEnd()
  48. lengthOfMonth()
  49. lengthOfYear()
  50. localToOffsetDate()
  51. offsetToLocalDate()

Format Functions

  1. b64Encode()
  2. b64EncodeNoPadding()
  3. b64MimeEncode()
  4. b64MimeEncodeNoPadding()
  5. b64UrlEncode()
  6. b64UrlEncodeNoPadding()
  7. b64Decode()
  8. b64MimeDecode()
  9. b64UrlDecode()
  10. urlEncode()
  11. urlDecode()
  12. caseValue()
  13. indexedValue()
  14. cycleValue()
  15. formatDate()
  16. formatNumber()
  17. formatText()
  18. formatTexts()
  19. toNumber()
  20. toString()
  21. toText()

Logical Functions

  1. contains()
  2. containsIgnoreCase()
  3. notContains()
  4. notContainsIgnoreCase()
  5. startsWith()
  6. startsWithIgnoreCase()
  7. notStartsWith()
  8. notStartsWithIgnoreCase()
  9. endsWith()
  10. endsWithIgnoreCase()
  11. notEndsWith()
  12. notEndsWithIgnoreCase()
  13. equals()
  14. equalsIgnoreCase()
  15. notEquals()
  16. notEqualsIgnoreCase()
  17. in()
  18. inIgnoreCase()
  19. notIn()
  20. notInIgnoreCase()
  21. isEmpty()
  22. isNotEmpty()
  23. isBlank()
  24. isNotBlank()
  25. isNull()
  26. isNotNull()
  27. isText()
  28. isBoolean()
  29. isNumber()
  30. isEven()
  31. isOdd()
  32. not()
  33. isWeekday()
  34. isWeekend()
  35. isLeapYear()

Array Functions

  1. size()
  2. lastIndex()
  3. indexOf()
  4. lastIndexOf()
  5. first()
  6. last()
  7. max()
  8. min()
  9. sum()
  10. avg()
  11. count()
  12. reverse()
  13. slice()
  14. sort()
  15. distinct()
  16. join()
  17. findByMax()
  18. findByMin()
  19. findByNullOrMax()
  20. findByNullOrMin()
  21. findByMaxOrNull()
  22. findByMinOrNull()

Structural Functions

  1. json()
  2. entries()
  3. keys()
  4. toArray()
  5. flatten()
  6. map()
  7. field()
  8. coalesce()
  9. csv()

Following are some examples of each function.

Arithmetic Functions

1. abs()

-3.14.abs() ==> 3.14

abs(3.14) ==> 3.14

2. calc()

1.5.calc(? * 2 + ? / 2) ==> 3.75

calc(2^8) ==> 256.0

calc(sqrt(a^2 + b^2), a:3, b:4) ==> 5.0

3. ceil()

3.14.ceil() ==> 4

ceil(-3.14) ==> -3

4. floor()

3.14.floor() ==> 3

floor(-3.14) ==> -4

5. mod()

8.mod(3) ==> 2

8.mod(?, 3) ==> 2

mod(-8, 3) ==> 1

3.mod(-8, ?) ==> 1

6. round()

3.14.round(1) ==> 3.1

3.14.round(?, 1) ==> 3.1

round(3.56, 0) ==> 4

String Functions

7. abbreviate()

'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"

8. appendIfMissing()

'abc'.appendIfMissing('xyz') ==> "abcxyz"

'abc'.appendIfMissing(?, 'xyz') ==> "abcxyz"

appendIfMissing('abcxyz', 'xyz') ==> "abcxyz"

'xyz'.appendIfMissing('abcXYZ', ?) ==> "abcXYZxyz"

9. appendIfMissingIgnoreCase()

'abc'.appendIfMissingIgnoreCase('xyz') ==> "abcxyz"

'abc'.appendIfMissingIgnoreCase(?, 'xyz') ==> "abcxyz"

appendIfMissingIgnoreCase('abcxyz', 'xyz') ==> "abcxyz"

'xyz'.appendIfMissingIgnoreCase('abcXYZ', ?) ==> "abcXYZ"

10. capitalize()

'cat'.capitalize() ==> "Cat"

capitalize('cAt') ==> "CAt"

11. center()

'abc'.center(7) ==> "  abc  "

'abc'.center(7, 'X') ==> "XXabcXX"

'abc'.center(?, 7, upperCase(?)) ==> "ABabcAB"

center('abc', 7, '') ==> "  abc  "

4.center('a', ?, 'yz') ==> "yayz"

12. concat()

'Hello'.concat(2022, '... ', ?, ' World!') ==> "2022... Hello World!"

13. keepAfter()

'abcxmnxyz'.keepAfter('x') ==> "mnxyz"

'abcxmnxyz'.keepAfter(?, 'X') ==> ""

keepAfter('abcxmnxyz', 'mn') ==> "xyz"

14. keepAfterIgnoreCase()

'abcxmnxyz'.keepAfterIgnoreCase('x') ==> "mnxyz"

'abcxmnxyz'.keepAfterIgnoreCase(?, 'X') ==> "mnxyz"

keepAfterIgnoreCase('abcxmnxyz', 'mn') ==> "xyz"

15. keepAfterLast()

'abcxmnxyz'.keepAfterLast('x') ==> "yz"

'abcxmnxyz'.keepAfterLast(?, 'X') ==> ""

keepAfterLast('abcxmnxyz', 'mn') ==> "xyz"

16. keepAfterLastIgnoreCase()

'abcxmnxyz'.keepAfterLastIgnoreCase('x') ==> "yz"

'abcxmnxyz'.keepAfterLastIgnoreCase(?, 'X') ==> "yz"

keepAfterLastIgnoreCase('abcxmnxyz', 'mn') ==> "xyz"

17. keepBefore()

'abcxmnxyz'.keepBefore('x') ==> "abc"

'abcxmnxyz'.keepBefore(?, 'X') ==> ""

keepBefore('abcxmnxyz', 'mn') ==> "abcx"

18. eepBeforeIgnoreCase()

'abcxmnxyz'.keepBeforeIgnoreCase('x') ==> "abc"

'abcxmnxyz'.keepBeforeIgnoreCase(?, 'X') ==> "abc"

keepBeforeIgnoreCase('abcxmnxyz', 'mn') ==> "abcx"

19. keepBeforeLast()

'abcxmnxyz'.keepBeforeLast('x') ==> "abcxmn"

'abcxmnxyz'.keepBeforeLast(?, 'X') ==> ""

keepBeforeLast('abcxmnxyz', 'mn') ==> "abcx"

20. keepBeforeLastIgnoreCase()

'abcxmnxyz'.keepBeforeLastIgnoreCase('x') ==> "abcxmn"

'abcxmnxyz'.keepBeforeLastIgnoreCase(?, 'X') ==> "abcxmn"

keepBeforeLastIgnoreCase('abcxmnxyz', 'mn') ==> "abcx"

21. leftPad()

'bat'.leftPad(5) ==> "  bat"

'bat'.leftPad(?, 8, 'yz') ==> "yzyzybat"

leftPad('bat', 3, 'yz') ==> "bat"

5.leftPad('bat', ?, '') ==> "  bat"

22. length()

'Josson'.length() ==> 6

length('Josson') ==> 6

length(2022) ==> 4

23. lowerCase()

'Cat'.lowerCase() ==> "cat"

lowerCase('cAt') ==> "cat"

24. notEmpty()

'abc'.notEmpty('xyz') ==> "abc"

''.notEmpty(null, '', 'xyz') ==> "xyz"

json('{"a":"","b":"","c":"abc"}').notEmpty(a,b,c,'xyz') ==> "abc"

25. notBlank()

'abc'.notBlank('xyz') ==> "abc"

' '.notBlank(null, '  ', 'xyz') ==> "xyz"

json('{"a":" ","b":" ","c":"abc"}').notBlank(a,b,c,'xyz') ==> "abc"

26. prependIfMissing()

'abc'.prependIfMissing('xyz') ==> "xyzabc"

'abc'.prependIfMissing(?, 'xyz') ==> "xyzabc"

prependIfMissing('xyzabc', 'xyz') ==> "xyzabc"

'xyz'.prependIfMissing('XYZabc', ?) ==> "xyzXYZabc"

27. prependIfMissingIgnoreCase()

'abc'.prependIfMissingIgnoreCase('xyz') ==> "xyzabc"

'abc'.prependIfMissingIgnoreCase(?, 'xyz') ==> "xyzabc"

prependIfMissingIgnoreCase('xyzabc', 'xyz') ==> "xyzabc"

'xyz'.prependIfMissingIgnoreCase('XYZabc', ?) ==> "XYZabc"

28. removeEnd()

'www.domain.com'.removeEnd('.com') ==> "www.domain"

'www.domain.com'.removeEnd(?, '.Com') ==> "www.domain.com"

removeEnd('www.domain.com', '.com') ==> "www.domain"

29. removeEndIgnoreCase()

'www.domain.COM'.removeEndIgnoreCase('.com') ==> "www.domain"

'www.domain.com'.removeEndIgnoreCase(?, '.Com') ==> "www.domain"

removeEndIgnoreCase('www.domain.com', '.COM') ==> "www.domain"

30. removeStart()

'www.domain.com'.removeStart('www.') ==> "domain.com"

'www.domain.com'.removeStart(?, '.Www') ==> "www.domain.com"

removeStart('www.domain.com', 'www.') ==> "domain.com"

31. removeStartIgnoreCase()

'WWW.domain.com'.removeStartIgnoreCase('www.') ==> "domain.com"

'www.domain.com'.removeStartIgnoreCase(?, '.Www') ==> "www.domain.com"

removeStartIgnoreCase('www.domain.com', 'WWW.') ==> "domain.com"

32. repeat()

'a'.repeat(3) ==> "aaa"

'ab'.repeat(?, 2) ==> "abab"

repeat('abc', 2) ==> "abcabc"

3.repeat('abc', ?) ==> "abcabcabc"

33. replace()

'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"

34. replaceIgnoreCase()

'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"

35. rightPad()

'bat'.rightPad(5) ==> "bat  "

'bat'.rightPad(?, 8, 'yz') ==> "batyzyzy"

rightPad('bat', 3, 'yz') ==> "bat"

rightPad('bat', 5, '') ==> "bat  "

36. split()

'abc def'.split() ==> [ "abc", "def" ]

'abc  def'.split(' ') ==> [ "abc", "def" ]

' abc  def '.split(?, ' ') ==> [ "abc", "def" ]

split('ab:cd:ef', ':') ==> [ "ab", "cd", "ef" ]

37. strip()

'  abc  '.strip(' ') ==> "abc"

'  abcyx'.strip('xyz') ==> "  abc"

strip('z abcyx', 'xyz') ==> " abc"

38. stripEnd()

'  abc  '.stripEnd(' ') ==> "  abc"

'z abcyx'.stripEnd('xyz') ==> "z abc"

stripEnd('z abcyx', 'xyz') ==> "z abc"

39. stripStart()

'  abc  '.stripStart(' ') ==> "abc  "

'z abcyx'.stripStart('xyz') ==> " abcyx"

stripStart('z abcyx', 'xyz') ==> " abcyx"

40. substr()

'abc'.substr(1) ==> "bc"

'abc'.substr(0, 2) ==> "ab"

'abc'.substr(?, 1, 2) ==> "b"

substr('abc', -2, -1) ==> "b"

2.substr('abc', -4, ?) ==> "ab"

41. trim()

'abc'.trim() ==> "abc"

trim('  abc  ') ==> "abc"

42. uncapitalize()

'Cat'.uncapitalize() ==> "cat"

uncapitalize('CAt') ==> "cAt"

43. upperCase()

'Cat'.upperCase() ==> "CAT"

upperCase('cAt') ==> "CAT"

Date Functions

44. amPmOfDay()

'2022-01-02T03:04:05'.amPmOfDay() ==> "AM"

amPmOfDay('2022-02-04T13:14:15') ==> "PM"

45. second()

'2022-01-02T03:04:05'.second() ==> 5

second('2022-02-04T13:14:15') ==> 15

46. secondOfDay()

'2022-01-02T03:04:05'.secondOfDay() ==> 11045

secondOfDay('2022-02-04T13:14:15') ==> 47655

47. minute()

'2022-01-02T03:04:05'.minute() ==> 4

minute('2022-02-04T13:14:15') ==> 14

48. minuteOfDay()

'2022-01-02T03:04:05'.minuteOfDay() ==> 184

minuteOfDay('2022-02-04T13:14:15') ==> 794

49. hourOfAmPm()

'2022-01-02T03:04:05'.hourOfAmPm() ==> 3

hourOfAmPm('2022-02-04T13:14:15') ==> 1

50. hour()

'2022-01-02T03:04:05'.hour() ==> 3

hour('2022-02-04T13:14:15') ==> 13

51. dayOfWeek()

'2022-01-02T03:04:05'.dayOfWeek() ==> 7

dayOfWeek('2022-02-04T13:14:15') ==> 5

52. day()

'2022-01-02T03:04:05'.day() ==> 2

day('2022-02-04T13:14:15') ==> 4

53. dayOfYear()

'2022-01-02T03:04:05'.dayOfYear() ==> 2

dayOfYear('2022-02-04T13:14:15') ==> 35

54. month()

'2022-01-02T03:04:05'.month() ==> 1

month('2022-02-04T13:14:15') ==> 2

55. year()

'2022-01-02T03:04:05'.year() ==> 2022

year('2022-02-04T13:14:15') ==> 2022

56. plusSeconds()

'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"

57. plusMinutes()

'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"

58. plusHours()

'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"

59. plusDays()

'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"

60. plusWeeks()

'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"

61. plusMonths()

'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"

62. plusYears()

'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"

63. minusSeconds()

'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"

64. minusMinutes()

'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"

65. minusHours()

'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"

66. minusDays()

'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"

67. minusWeeks()

'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"

68. minusMonths()

'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"

69. minusYears()

'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"

70. truncateToMicro()

'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"

71. truncateToMilli()

'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"

72. truncateToSecond()

'2022-01-02T03:04:05.229390600'.truncateToSecond() ==> "2022-01-02T03:04:05"

truncateToSecond('2022-02-04T13:14:15.229390600') ==> "2022-02-04T13:14:15"

73. truncateToMinute()

'2022-01-02T03:04:05.229390600'.truncateToMinute() ==> "2022-01-02T03:04"

truncateToMinute('2022-02-04T13:14:15.229390600') ==> "2022-02-04T13:14"

74. truncateToHour()

'2022-01-02T03:04:05.229390600'.truncateToHour() ==> "2022-01-02T03:00"

truncateToHour('2022-02-04T13:14:15.229390600') ==> "2022-02-04T13:00"

75. truncateToDay()

'2022-01-02T03:04:05.229390600'.truncateToDay() ==> "2022-01-02T00:00"

truncateToDay('2022-02-04T13:14:15.229390600') ==> "2022-02-04T00:00"

76. truncateToMonth()

'2022-01-02T03:04:05.229390600'.truncateToMonth() ==> "2022-01-01T00:00"

truncateToMonth('2022-02-04T13:14:15.229390600') ==> "2022-02-01T00:00"

77. truncateToYear()

'2022-01-02T03:04:05.229390600'.truncateToYear() ==> "2022-01-01T00:00"

truncateToYear('2022-02-04T13:14:15.229390600') ==> "2022-01-01T00:00"

78. withNano()

'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"

79. withMicro()

'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"

80. withMilli()

'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"

81. withSecond()

'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"

82. withMinute()

'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"

83. withHour()

'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"

84. withDay()

'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"

85. withDayOfYear()

'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"

86. withMonth()

'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"

87. withYear()

'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"

88. dayEnd()

'2022-01-02T03:04'.dayEnd() ==> "2022-01-02T23:59:59.999999999"

dayEnd('2022-02-04T13:14') ==> "2022-02-04T23:59:59.999999999"

89. monthEnd()

'2022-01-02T03:04'.monthEnd() ==> "2022-01-31T23:59:59.999999999"

monthEnd('2022-02-04T13:14') ==> "2022-02-28T23:59:59.999999999"

90. yearEnd()

'2022-01-02T03:04'.yearEnd() ==> "2022-12-31T23:59:59.999999999"

yearEnd('2022-02-04T13:14') ==> "2022-12-31T23:59:59.999999999"

91. lengthOfMonth()

'2022-01-02T03:04'.lengthOfMonth() ==> 31

lengthOfMonth('2022-02-04T13:14') ==> 28

92. lengthOfYear()

'2022-01-02T03:04'.lengthOfYear() ==> 365

lengthOfYear('2024-02-04T13:14') ==> 366

93. localToOffsetDate()

'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"

94. offsetToLocalDate()

'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"

Format Functions

95. b64Encode()

'[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ'.b64Encode()
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=="

96. b64EncodeNoPadding()

b64EncodeNoPadding('[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ')
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg"

97. b64MimiEncode() - Split lines into 76 character wide chunks

'[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ'.b64MimeEncode()
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\r\nUVJTVFVWV1hZWg=="

98. b64MimeEncodeNoPadding()

b64MimeEncodeNoPadding('[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ')
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\r\nUVJTVFVWV1hZWg"

99. b64UrlEncode()

'[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ'.b64UrlEncode()
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=="

100. b64UrlEncodeNoPadding()

b64UrlEncodeNoPadding('[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ')
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg"

101. b64Decode()

'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=='.b64Decode()
==>
"[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

b64Decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg')
==>
"[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

102. b64MimeDecode()

'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\nUVJTVFVWV1hZWg=='.b64MimeDecode()
==>
"[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

b64MimeDecode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\r\nUVJTVFVWV1hZWg')
==>
"[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

103. b64UrlDecode()

'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=='.b64UrlDecode()
==>
"[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

b64UrlDecode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg')
==>
"[email protected]#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

104. urlEncode()

'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"

105. urlDecode()

'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"

106. caseValue()

'a'.caseValue('c',1,'b',2,'a',3,4) ==> 3

'z'.caseValue('c',1,'b',2,'a',3,4) ==> 4

'z'.caseValue('c',1,'b',2,'a',3) ==> !unresolvable!

json('[{"s":1},{"s":null},{"s":3}]').s.caseValue(1,'A',null,'B') ==> [ "A", "B", null ]

107. indexedValue()

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!

108. cycleValue()

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"

109. formatDate()

'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"

110. formatNumber()

12345.6.formatNumber('HK$#,##0.00') ==> "HK$12,345.60"

123.formatNumber(?, '#,##0.#') ==> "123"

formatNumber(123.45, '#,##0.#') ==> "123.5"

111. formatText()

'Dog'.formatText('[%-5s]') ==> "[Dog  ]"

123.formatText(?, '[%5d]') ==> "[  123]"

formatText('Dog', '[%5s]') ==> "[  Dog]"

112. formatTexts()

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"

113. toNumber()

'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 ]

114. toString()

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]"

115. toText()

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" ]

Logical

116. contains()

'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

117. containsIgnoreCase()

'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

118. notContains()

'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

119. notContainsIgnoreCase()

'abcde'.notContainsIgnoreCase('bc') ==> false

notContainsIgnoreCase('abcde','B') ==> false

json('["a","b","c"]').notContainsIgnoreCase(?, 'D') ==> true

120. startsWith()

'abcdef'.startsWith('abc') ==> true

'ABCDEF'.startsWith(?,'abc') ==> false

startsWith('ABCDEF','cde') ==> false

121. startsWithIgnoreCase()

'abcdef'.startsWithIgnoreCase('abc') ==> true

'ABCDEF'.startsWithIgnoreCase(?,'abc') ==> true

startsWithIgnoreCase('ABCDEF','cde') ==> false

122. notStartsWith()

'abcdef'.notStartsWith('abc') ==> false

'ABCDEF'.notStartsWith(?,'abc') ==> true

notStartsWith('ABCDEF','cde') ==> true

123. notStartsWithIgnoreCase()

'abcdef'.notStartsWithIgnoreCase('abc') ==> false

'ABCDEF'.notStartsWithIgnoreCase(?,'abc') ==> false

notStartsWithIgnoreCase('ABCDEF','cde') ==> true

124. endsWith()

'abcdef'.endsWith('def') ==> true

'ABCDEF'.endsWith(?,'def') ==> false

endsWith('ABCDEF','cde') ==> false

125. endsWithIgnoreCase()

'abcdef'.endsWithIgnoreCase('def') ==> true

'ABCDEF'.endsWithIgnoreCase(?,'def') ==> true

endsWithIgnoreCase('ABCDEF','cde') ==> false

126. notEndsWith()

'abcdef'.notEndsWith('def') ==> false

'ABCDEF'.notEndsWith(?,'def') ==> true

notEndsWith('ABCDEF','cde') ==> true

127. notEndsWithIgnoreCase()

'abcdef'.notEndsWithIgnoreCase('def') ==> false

'ABCDEF'.notEndsWithIgnoreCase(?,'def') ==> false

notEndsWithIgnoreCase('ABCDEF','cde') ==> true

128. equals()

'abc'.equals('abc') ==> true

'abc'.equals(?, ' abc') ==> false

equals('ABC','abc') ==> false

129. equalsIgnoreCase()

'abc'.equalsIgnoreCase('abc') ==> true

'abc'.equalsIgnoreCase(?, ' abc') ==> false

equalsIgnoreCase('ABC','abc') ==> true

130. notEquals()

'abc'.notEquals('abc') ==> false

'abc'.notEquals(?, ' abc') ==> true

notEquals('ABC','abc') ==> true

131. notEqualsIgnoreCase()

'abc'.notEqualsIgnoreCase('abcd') ==> true

'abc'.notEqualsIgnoreCase(' abc') ==> true

notEqualsIgnoreCase('ABC','abc') ==> false

132. in()

56.in(12,34,56) ==> true

'56'.in(12,34,56) ==> true

'A'.in(json('["a","b","c"]')) ==> false

133. inIgnoreCase()

'A'.inIgnoreCase('a','b','c') ==> true

'a '.inIgnoreCase('a','b','c') ==> false

134. notIn()

56.notIn(12,34,56) ==> false

'56'.notIn(12,34,56) ==> false

'A'.notIn(json('["a","b","c"]')) ==> true

135. notInIgnoreCase()

'A'.notInIgnoreCase('a','b','c') ==> false

'a '.notInIgnoreCase('a','b','c') ==> true

136. isEmpty()

''.isEmpty() ==> true

isEmpty(' ') ==> false

137. isNotEmpty()

''.isNotEmpty() ==> false

isNotEmpty(' ') ==> true

138. isBlank()

''.isBlank() ==> true

isBlank(' ') ==> true

139. isNotBlank()

''.isNotBlank() ==> false

isNotBlank(' ') ==> false

140. isNull()

null.isNull() ==> !unresolvable!

isNull(null) ==> true

isNull('') ==> false

141. isNotNull()

null.isNotNull() ==> !unresolvable!

isNotNull(null) ==> false

isNotNull('') ==> true

142. isText()

'text'.isText() ==> true

isText(1) ==> false

isText(true) ==> false

isText(null) ==> false

143. isBoolean()

'text'.isBoolean() ==> false

isBoolean(1) ==> false

isBoolean(true) ==> true

isBoolean(null) ==> false

144. isNumber()

'text'.isNumber() ==> false

isNumber(1) ==> true

isNumber(true) ==> false

isNumber(null) ==> false

145. isEven()

1.isEven() ==> false

isEven(2) ==> true

146. isOdd()

1.isOdd() ==> true

isOdd(2) ==> false

147. not()

true.not() ==> false

not(false) ==> true

not('false') ==> false

not(0) ==> false

not(null) ==> false

148. isWeekday

'2021-12-31T00:00:00'.isWeekday() ==> true

isWeekday('2022-01-01T00:00:00') ==> false

149. isWeekend

'2021-12-31T00:00:00'.isWeekend() ==> false

isWeekend('2022-01-01T00:00:00') ==> true

150. isLeapYear

'2020-12-31T00:00:00'.isLeapYear() ==> true

isLeapYear('2022-01-01T00:00:00') ==> false

Array Functions

151. size()

json('[7,1,9,null,5,3]').size() ==> 6

size(json('[7,1,9,null,5,3]')) ==> 6

152. lastIndex()

json('[7,1,9,null,5,3]').lastIndex() ==> 5

lastIndex(json('[7,1,9,null,5,3]')) ==> 5

153. indexOf()

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

154. lastIndexOf()

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

155. first()

json('[7,1,9,null,5,3]').first() ==> 7

first(json('[null,7,1,9,5,3]')) ==> null

156. last()

json('[7,1,9,null,5,3]').last() ==> 3

last(json('[7,1,9,5,3,null]')) ==> null

157. max()

json('[7,1,9,null,5,3]').max() ==> 9

max(json('[7,1,9,null,5,3]'), 15, 16) ==> 16

158. min()

json('[7,1,9,null,5,3]').min() ==> 1

min(json('[7,1,9,null,5,3]'), 15, 16) ==> 1

159. sum()

json('[7,1,9,null,5,3]').sum() ==> 25.0

sum(json('[7,1,9,null,5,3]'), 15, 16) ==> 56.0

160. avg()

json('[7,1,9,null,5,3]').avg() ==> 5.0

avg(json('[7,1,9,null,5,3]'), 15, 16) ==> 8.0

161. count()

json('[7,1,9,null,5,3]').count() ==> 5

count(json('[7,1,9,null,5,3]'), 15, 16) ==> 7

162. reverse()

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 ]

163. slice()

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 ]

164. sort()

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"
} ]

165. distinct()

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 ]

166. join()

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"

167. findByMax()

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
}

168. findByMin()

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
}

169. findByNullOrMax()

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
}

170. findByNullOrMin()

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
}

171. findByMaxOrNull()

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
}

172. findByMinOrNull()

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
}

Structural Functions

173. 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
  }
}

174. entries()

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
  }
} ]

175. keys()

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" ]

176. toArray()

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 ]

177. flatten()

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 ] ] ]

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 ] ]

flatten(json('[[[[1,2],[3,4]],[[5,6],[7,8]]],[[[9,10],[11,12]],[[13,14],[15,16]]]]'), 3)
==>
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]

178. map()

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 ]
  }
}

179. field()

json('{"a":1,"b":[2,3],"c":{"d":4,"e":5}}').field(f:6,c:)
==>
{
  "a" : 1,
  "b" : [ 2, 3 ],
  "f" : 6
}

180. coalesce()

json('["abc","",123,false,null]').coalesce('xyz') ==> [ "abc", "", 123, false, "xyz" ]

json('{"a":null,"c":"abc"}').coalesce(a,b,c,'xyz') ==> "abc"

181. csv()

json('{"len1":"12.3\\"","len2":"26.1\\"","len3":"64.0\\""}').csv() ==> "12.3""","26.1""","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

Jossons Basic

Jossons stores JSON datasets in a map of type Map<String, Josson> for placeholder resolution.

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);

Jossons Template Language

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}}

Nested Placeholders

Placeholders can be nested and are resolved from inside to outside. Resolved one is replaced with text and continue for the next round.

Example:

{{stock->[itemCode='{{order->items[qrCode='{{qrCode}}'].itemCode}}'].qty}}
  1. {{qrCode}} is resolved to 1234567890
  2. {{order->items[qrCode='1234567890'].itemCode}} is resolved to ABCDE
  3. {{stock->[itemCode='ABCDE'].qty}} is resolved to 100

Ternary Syntax

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?}}

Implicit Variables

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

Fill In

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.

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)}}"
  1. If company->address[0] is unresolvable, 56 spaces are printed.

    {{company->address[0].rightPad(56) ?: $->repeat(' ',56)}}
    
  2. Due date is calculated from one month after the salesDate.

    {{order->salesDate.plusMonths(1).formatDate('dd/MM/yyyy')}}
    
  3. "SHIP TO" is not printed if order->delivery does not exists.

    {{order->delivery!=null ? 'SHIP TO'}}
    
  4. 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)}}
    
  5. 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() ==>""
    
  6. 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(##, $V...)]->join() ==>""
  1. 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)}}
    
  2. 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

Jossons Resolver

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 progress has three debug levels:

  1. ResolverDebugLevel.SHOW_CONTENT_OF_VALUE_NODE_ONLY (default)
  2. ResolverDebugLevel.SHOW_CONTENT_UP_TO_OBJECT_NODE
  3. ResolverDebugLevel.SHOW_CONTENT_UP_TO_ARRAY_NODE

    ResolverProgress progress = new ResolverProgress();

    ResolverProgress progress = new ResolverProgress("subject");

    progress.debugLevel(ResolverDebugLevel.SHOW_CONTENT_UP_TO_OBJECT_NODE);

    progress.autoMarkEnd(false);

    List steps = progress.getSteps();

An Example of the progress output:

Round 1 : Resolving salesOrder from sales_order?{orderId:'202208172',orderSubs:{$elemMatch:{subsId:'S00000263'}}},{_id:0,'orderSubs.$':1}
Round 1 : Resolved salesOrder = Object with 18 elements
Round 1 : Resolving {offers=orderSub->offers, plan=subsDetail->planName, txnDate=salesOrder->orderPayment.txnDate.formatDate('yyyy-MM-dd'), txnId=salesOrder->orderPayment.paymentId, products=orderSub->offers.products.productName}
Round 1 : Resolved txnDate = "2021-08-18"
Round 1 : Resolved txnId = "1210818002190"
Round 2 : Resolving custSub from cust_subscription?{subsId:'S00000263'}
Round 2 : Resolved custSub = Object with 32 elements
Round 2 : Resolving {orderSub=salesOrder->orderSubs[subsId='S00000263'], subsDetail=custSub->subsDetails[deleteFlag!=true]*.findByMaxOrNull(startDate)}
Round 2 : Resolved orderSub = Object with 33 elements
Round 2 : Resolved subsDetail = Object with 21 elements
Round 3 : Resolving customer from ?{custId:'C00000045'}
Round 3 : Resolved customer = Object with 27 elements
Round 3 : Resolving {offers=orderSub->offers, plan=subsDetail->planName}
Round 3 : Resolved offers = Array with 2 elements
Round 3 : Resolved plan = "Promotion"
Round 3 : Resolved products = Array with 8 elements
Round 4 : End

Dictionary Finder

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 either:

  • A statement that represent a value.

    "1"      // IntNode
    "2.3"    // DoubleNode
    "'Text'" // TextNode
    "true"   // BooleanNode
    "null"   // NullNode
    
  • A Jossons query that retrieve data from other datasets.

    "otherKey->jossonQuery"
    
  • A database query statement, please refer to Data Finder.

    "collectionName ? {findStatement}"
    
  • A join operation query to merge two datasets, please refer to Join Datasets.

    "leftQuery{keyL1,keyL2...} <=< rightQuery{keyR1,keyR2...}"
    

Data Finder

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 ? {findStatement},{projectStatment}"

"collectionName ? [aggregateStatements]"

"? {findStatement}"

"? {findStatement},{projectStatment}"

"? [aggregateStatements]"

Many-documents query syntax that request for an ArrayNode:

"collectionName[] ? {findStatement}"

"collectionName[] ? {findStatement},{projectStatment}"

"collectionName[] ? [aggregateStatements]"

"[] ? {findStatement}"

"[] ? {findStatement},{projectStatment}"

"[] ? [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.

Join Datasets

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.

There are two join types.

  • Join One - Find the first matched object node and merge the object elements.
  • Join Many - Find all matched nodes and embed into the object as an array node.

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.

For Join Many operations, the arrayName: is optional. If arrayName is not given, the last element name of the query is used.

  • Inner Join One >=<

    "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...}"
    

Examples:

"order->offers{offerId} <=< offers->map(offerId, offerName, offerDesc){offerId}"

"customers{custId} <=<< followups{custId}"

Appendix

MongoDB Adapter

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;
        };
    }
}