tonysparks / jslt2

VM based implementation of JSLT
MIT License
7 stars 0 forks source link

jslt2

VM based implementation of JSLT - which is a JSON query and transformation language.

Example Translations

Given input:

{
    "name" : "Brett Favre",
    "team" : "Green Bay Packers"
}

and given the JSLT script:

{
    "player" : .name + " played for " + .team
}

outputs:

{
    "player" : "Brett Favre played for Green Bay Packers"
}

Implemented

How to use

Include in your Maven/Gradle project:

<!-- https://mvnrepository.com/artifact/com.github.tonysparks.jslt2/jslt2 -->
<dependency>
    <groupId>com.github.tonysparks.jslt2</groupId>
    <artifactId>jslt2</artifactId>
    <version>0.2.8</version>
</dependency>

How to initialize and evaluate a template expression:

// use out of the box defaults
Jslt2 runtime = new Jslt2(); 

// or, customize the runtime...

Jslt2 runtime = Jslt2.builder()
            .resourceResolver(ResourceResolvers.newFilePathResolver(new File("./examples")))
            .enableDebugMode(true)
            .objectMapper(new ObjectMapper())
            .maxStackSize(1024 * 5)
            .build();

// Execute a template:
JsonNode input = ...; // some input Json
JsonNode result = runtime.eval("{ \"input\" : . }", input);

// or, compile a template for repeated use:
Template template = runtime.compile("{ \"input\" : . }");

JsonNode result1 = template.eval(input);
JsonNode input2 = ...; // different json input
JsonNode result2 = template.eval(input2);

Differences between JSLT and JSLT2

let x = """this
   is a "verbatim"
   string"""
{for ([1,2,3])
    ("key_" + $index__) : .
}

// outputs:
{
    "key_0":1,
    "key_1":2,
    "key_2":3
}

If you need to embed for expressions, you can reference the parent index by:

{for ([1,2,3])
    let parentIndex = $index__
    ("key_" + $index__) : [for (["a", "b", "c"]) $parentIndex * $index__ ]
}

// outputs:

{
    "key_0":[0,0,0],
    "key_1":[0,1,2],
    "key_2":[0,2,4]
}

As an example:

// runs the makeSlowDatabaseQuery functions in background threads, which allows them to be computed
// in parallel 
async {
  let a = makeSlowDatabaseQuery(.someParam)     
  let b = makeSlowDatabaseQuery(.someOtherParam)
} // evaluation will block here until all let expressions have been computed

{
    "a": $a, // we can now reference the computed value of $a in our template expression
    "b": $b,
}

There are several limitations or "gotchas" with async blocks:

async {
  let a = "foo"     
  let b = "bar" + $a // INVALID, can't reference $a as this value will be computed in parallel with $b
}
let y = "bar"
async {
  let a = $x // INVALID because x is defined AFTER the async block
  let b = $y // valid, because y is defined BEFORE the async block  
}

let x = "foo"

async {
  let a = "hi"  
}

let x = $a // can reference $a in a new variable
def y() $a // can reference $a in a function

{
    "result": $a // can reference $a in the template expression
}

You can customize the ExecutorService provided to the Jslt2 runtime. The default ExecutorService uses daemon threads and Executors.newCachedThreadPool.

// Customize (or use an already created instance) of ExecutorService
ExecutorService service = ...
Jslt2 runtime = Jslt2.builder()
    .executorService(service)    
    .build();