A query language for JSON and a template engine to generate text output.

Overview

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 ==>""
    
  2. Node name is case-sensitive. Josson returns null value if the path is unresolvable.

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

    Path chart

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

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

    Path chart

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

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

    Path chart

     {}->customer.name ==>""
    
  5. 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() ==>""
    
  6. 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) ==>""
    
  7. If the function is the first path step, it works on the root node.

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

    Path chart

     {}->upperCase($V) ==>""
    
  8. 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...) ==>""
    
  9. A path start with numbers override the data and produces an integer node.

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

    Path chart

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

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

    Path chart

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

    "" ==>""
    
  12. 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
    
  13. 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* ==>[{}]
    
  14. 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] ==>{}
    
  15. To query a value node in an array element.

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

    Path chart

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

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

    Path chart

    {}->items[2].property{} ==>{}
    
  17. 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]
    
  18. 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)] ==>[""]
    
  19. 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(?)] ==>[""]
    
  20. 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
    
  21. Uses Java standard formatting pattern.

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

    Path chart

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

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

    Path chart

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

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

    Path chart

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

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

    Path chart

    {}->items[]!!.name ==>!unresolvable!
    
  25. 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] ==>[""]
    
  26. 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[]*->[""]] ==>[""]
    
  27. 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*->[""]] ==>[""]
    
  28. 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] ==>[""]
    
  29. 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*->[""]] ==>[""]
    
  30. 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*->[""]
    
  31. 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() ==>""
    
  32. 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()->""
    
  33. 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()->""
    
  34. 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()->""
    
  35. 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').@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()
    
  36. Function parameter can be a value node of parent.

    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($V...)->""
              /                     \
    {}->items@                       @->[""]->join()==>""
              \                     /
               {}->repeat($V...)->""
    
  37. 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() ==>""
    
  38. 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() ==>""
    
  39. An object node with a validation filter.

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

    Path chart

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

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

    Path chart

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

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

    Path chart

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

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

    Path chart

    {}->{}[]->{}->isNotNull() ==>$TF
    
  43. 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
    
  44. 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(?, ##)] ==>[""]
    
  45. 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(?, ##)] ==>[""]
    
  46. 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() ==>""
    
  47. 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(?, ##)] ==>[""]
    
  48. 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() ==>""
    
  49. 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($V...)->$D
              /                   \
    {}->items@                     @->[$D->concat(?, #a)] ==>[""]
              \                   /
               {}->calc($V...)->$D
    
  50. 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()] ==>[""]
    
  51. 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(?)] ==>[""]
    
  52. 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() ==>[{}]
    
  53. Function keys() lists an object's key names.

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

    Path chart

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

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

    Path chart

    {}->customer->toArray() ==>[""]
    
  56. 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...) ==>[""]
    
  57. 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...) ==>{}
    
  58. 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...) ==>{}
    
  59. 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...)] ==>[{}]
    
  60. 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*->[""]
              /               \
    {}->items@                 @->[[""]]->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()

'abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ'.b64Encode()
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=="

96. b64EncodeNoPadding()

b64EncodeNoPadding('abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ')
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg"

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

'abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ'.b64MimeEncode()
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\r\nUVJTVFVWV1hZWg=="

98. b64MimeEncodeNoPadding()

b64MimeEncodeNoPadding('abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ')
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\r\nUVJTVFVWV1hZWg"

99. b64UrlEncode()

'abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ'.b64UrlEncode()
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=="

100. b64UrlEncodeNoPadding()

b64UrlEncodeNoPadding('abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ')
==>
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg"

101. b64Decode()

'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=='.b64Decode()
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

b64Decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg')
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

102. b64MimeDecode()

'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\nUVJTVFVWV1hZWg=='.b64MimeDecode()
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

b64MimeDecode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp+IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9Q\r\nUVJTVFVWV1hZWg')
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

103. b64UrlDecode()

'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=='.b64UrlDecode()
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=ABCDEFGHIJKLMNOPQRSTUVWXYZ"

b64UrlDecode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp-IUAjJCVeJiooKV8rLT1BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg')
==>
"abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=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() ==>""
    
  7. 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)}}
    
  8. 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<String> 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;
        };
    }
}
You might also like...

A JSON Transmission Protocol and an ORM Library for automatically providing APIs and Docs.

A JSON Transmission Protocol and an ORM Library for automatically providing APIs and Docs.

🚀 零代码、热更新、全自动 ORM 库,后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构。 🚀 A JSON Transmission Protocol and an ORM Library for automatically providing APIs and Docs.

Dec 31, 2022

A Java serialization/deserialization library to convert Java Objects into JSON and back

Gson Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to a

Jan 8, 2023

Screaming fast JSON parsing and serialization library for Android.

Screaming fast JSON parsing and serialization library for Android.

#LoganSquare The fastest JSON parsing and serializing library available for Android. Based on Jackson's streaming API, LoganSquare is able to consiste

Dec 18, 2022

A modern JSON library for Kotlin and Java.

Moshi Moshi is a modern JSON library for Android and Java. It makes it easy to parse JSON into Java objects: String json = ...; Moshi moshi = new Mos

Dec 31, 2022

A JSON Schema validation implementation in pure Java, which aims for correctness and performance, in that order

Read me first The current version of this project is licensed under both LGPLv3 (or later) and ASL 2.0. The old version (2.0.x) was licensed under LGP

Jan 4, 2023

A universal types-preserving Java serialization library that can convert arbitrary Java Objects into JSON and back

A universal types-preserving Java serialization library that can convert arbitrary Java Objects into JSON and back, with a transparent support of any kind of self-references and with a full Java 9 compatibility.

Dec 30, 2021

Framework for serialization to Json, XML, Byte and Excel, therefore an oviparous wool milk sow J

Framework for serialization to Json, XML, Byte and Excel, therefore an oviparous wool milk sow J

NetworkParser Framework for serialization from Java objects to Json, XML and Byte. NetworkParser is a simple framework for serializing complex model s

Nov 18, 2020

Java libraries for serializing, deserializing, and manipulating JSON values

java-json-toolkit The json-toolkit repository contains the code to the following libraries: json-toolkit-text: basic library for conversion between te

Jan 26, 2022

A toy compiler that translates SysY (a subset of C language) into ARMv7a, implemented in Java15.

A toy compiler that translates SysY (a subset of C language) into ARMv7a, implemented in Java15.

北京航空航天大学 No Segmentation Fault Work 队作品。 ayame A toy compiler that translates SysY (a subset of C language) into ARMv7a. Build javac -encoding UTF-8 $

Jan 2, 2023
Comments
  • [Snyk] Security upgrade com.fasterxml.jackson.datatype:jackson-datatype-jsr310 from 2.13.4 to 2.14.0

    [Snyk] Security upgrade com.fasterxml.jackson.datatype:jackson-datatype-jsr310 from 2.13.4 to 2.14.0

    This PR was automatically created by Snyk using the credentials of a real user.


    Snyk has created this PR to fix one or more vulnerable packages in the `maven` dependencies of this project.

    Changes included in this PR

    • Changes to the following files to upgrade the vulnerable dependencies to a fixed version:
      • pom.xml

    Vulnerabilities that will be fixed

    With an upgrade:

    Severity | Priority Score (*) | Issue | Upgrade | Breaking Change | Exploit Maturity :-------------------------:|-------------------------|:-------------------------|:-------------------------|:-------------------------|:------------------------- medium severity | 616/1000
    Why? Proof of Concept exploit, Has a fix available, CVSS 5.9 | Denial of Service (DoS)
    SNYK-JAVA-COMFASTERXMLJACKSONCORE-3038426 | com.fasterxml.jackson.datatype:jackson-datatype-jsr310:
    2.13.4 -> 2.14.0
    | No | Proof of Concept

    (*) Note that the real score may have changed since the PR was raised.

    Check the changes in this PR to ensure they won't cause issues with your project.


    Note: You are seeing this because you or someone else with access to this repository has authorized Snyk to open fix PRs.

    For more information: 🧐 View latest project report

    🛠 Adjust project settings

    📚 Read more about Snyk's upgrade and patch logic


    Learn how to fix vulnerabilities with free interactive lessons:

    🦉 Denial of Service (DoS)

    opened by octomix 0
Releases(v1.3.20)
Owner
Octomix Software
Octomix Software
Convert Java to JSON. Convert JSON to Java. Pretty print JSON. Java JSON serializer.

json-io Perfect Java serialization to and from JSON format (available on Maven Central). To include in your project: <dependency> <groupId>com.cedar

John DeRegnaucourt 303 Dec 30, 2022
JSON query and transformation language

JSLT JSLT is a complete query and transformation language for JSON. The language design is inspired by jq, XPath, and XQuery. JSLT can be used as: a q

Schibsted Media Group 510 Dec 30, 2022
Generate Java types from JSON or JSON Schema and annotates those types for data-binding with Jackson, Gson, etc

jsonschema2pojo jsonschema2pojo generates Java types from JSON Schema (or example JSON) and can annotate those types for data-binding with Jackson 2.x

Joe Littlejohn 5.9k Jan 5, 2023
Text Object Java Objects (TOJOs): an object representation of a multi-line structured text file like CSV

It's a simple manager of "records" in a text file of CSV, JSON, etc. format. It's something you would use when you don't want to run a full database,

Yegor Bugayenko 19 Dec 27, 2022
JSON to JSON transformation library written in Java.

Jolt JSON to JSON transformation library written in Java where the "specification" for the transform is itself a JSON document. Useful For Transformin

Bazaarvoice 1.3k Dec 30, 2022
Essential-json - JSON without fuss

Essential JSON Essential JSON Rationale Description Usage Inclusion in your project Parsing JSON Rendering JSON Building JSON Converting to JSON Refer

Claude Brisson 1 Nov 9, 2021
A simple java JSON deserializer that can convert a JSON into a java object in an easy way

JSavON A simple java JSON deserializer that can convert a JSON into a java object in an easy way. This library also provide a strong object convertion

null 0 Mar 18, 2022
A 250 lines single-source-file hackable JSON deserializer for the JVM. Reinventing the JSON wheel.

JSON Wheel Have you ever written scripts in Java 11+ and needed to operate on some JSON string? Have you ever needed to extract just that one deeply-n

Roman Böhm 14 Jan 4, 2023
An Engine to run batch request with JSON based REST APIs

JsonBatch An Engine to run batch request with JSON based REST APIs Some usecase for this library: Provide a batch API to your REST API set. Quickly ro

Rey Pham 11 Jan 3, 2022
Generate getter and setter on Java with ease! PS: Intended to use for labs where typing them are still the norm!!

GenerateGetterSetter Generate getter and setter on Java with ease! PS: Intended to use for labs where typing them are still the norm!! How to use Clon

Vishnu Sanal. T 4 Jan 4, 2022