5 Ja.js - Selector Transformer

Ja.js (previously named st.js) is a library that enables implementing practically any programming concept in a declarative manner, using JSON.

This is because ja.js is a low level building block for creating a Turing Complete JSON markup language.

ja.js is the core JSON parser that powers Jasonette, so you can build native iOS/Android apps by writing nothing but a JSON markup.

Ja.js

Ja.js

But Jasonette is just one implementation. Want to build your own turing complete JSON markup language?

5.2 Playground

Parse JSON anywhere, both frontend and backend, as easy as using JSON.stringify or JSON.parse.

https://jasonelle.com/docs/playground-ja/

Source Code:

https://github.com/jasonelle/docs/tree/develop/playground-ja.

5.3 Introduction

ja.js is a library that adds a couple of powerful methods to JavaScript’s native JSON.

So you can simply use it with the syntax ST.select(...).transform(...).

The library is just a single file, made up of stateless functions, with NO dependency. Which makes it effortless to embed anywhere without hassle. (Currently used in various environments including iOS, Android, node.js, browser, etc.).

What Can I Use It For?

JSON powers almost everything in the world. Being able to bend any JSON to your will means you can do all kinds of magical things.

Ja.js consist mainly of two operations:

  1. Select: Query any JSON tree to select exactly the subtree you are looking for.
  2. Transform: Transform any JSON object to another by parsing with a template, also written in JSON

You can also mix and match Select AND Transform to perform partial transform, modularize JSON objects, etc.

5.3.2 Transform

Transform any JSON with a declarative template, also in JSON.

Step 1. Take any JSON object

Step 2. Select and transform with a template JSON object

Step 3. Get the result

5.4 Select

Select a JSON object or its subtree that matches your filter function.

5.4.1 Syntax

5.4.1.1 Parameters

  • data : Any JavaScript object
  • selectorFunction : selectorFunction is a predicate, to test each key/value pairs of the entire JSON tree. Return true to keep the element, false otherwise, taking two arguments:
    • key : the “key” of the current key/value pair being tested.
    • value : the “value” of the current key/value pair being tested.

5.4.1.2 Return Value

Returns a selection object which can be queried to retrieve the final result, using the following API:

  • values() : Array of values for all the key/value pair matches.
  • keys() : Array of keys for all the key/value pair matches.
  • paths() : Array of paths leading to all the key/value pair matches.
  • objects() : Array of full objects in which the key/value match was found.
  • root() : Retrieve the root node. Useful when used along with transform.

5.5 Transform

Transform any JSON with a declarative template, also in JSON.

5.5.1 Syntax

There are 2 ways of transforming an object:

5.5.1.1 transform()

Select a template or its subtree, and transform data with the selected template.

ST.select(TEMPLATE).transform(DATA)

5.5.1.2 transformWith()

Select a data object or its subtree and transform with a template.

ST.select(DATA).transformWith(TEMPLATE)

5.5.11 Local Variables

You can use #let API to declare local variables. The #let API takes an array as a paremeter, which has two elements:

  • The first parameter: the {{#let}} statement which assigns any value to a variable.

  • The second parameter: the actual expression that will be evaluated.

Here’s an example:

  1. Template and Data
  1. Select and Transform
  1. Result

The local variable feature is important when you are using nested loops.

You could use the $root variable to reach out of the current loop context, but this has limitations, because you can always only reach out to the root level.

By using the #let API, you can define a variable at any level of a loop and have it accessible from anywhere further down the loop WITHOUT using the $root variable.

5.5.13 Example 2 - Filter Data + Transform with template

Sometimes you have a large set of data but only want to transform a portion of it. In this case you can select a subtree of the data object and parse using a template.

var data = {
  "item": { "url": "http://localhost", "text": "localhost" },
  "items": [
    { "url": "file://documents", "text": "documents" },
    { "url": "https://blahblah.com", "text": "blah"  }
  ],
  "nestedItems": {
    "childItems": [{
      "url": "http://hahaha.com",
      "text": "haha"
    }, {
      "url": "https://hohoho.com",
      "text": "hoho"
    }]
  }
};

var selection = ST.select(data, function(key, val) {
  return key === 'url';
});

var urls = selection.values();
/**
*  urls = [
*    "http://localhost",
*    "file://documents",
*    "https://blahblah.com",
*    "http://hahaha.com",
*    "https://hohoho.com"
*  ]
*/

var transformed = selection.transformWith({
  "tag": "<a href='{{url}}'>{{text}}</a>"
})

var objects = transformed.objects()
/**
* objects = [
*   { "tag": "<a href='http://localhost'>localhost</a>" },
*   { "tag": "<a href='file://documents'>documents</a>" },
*   { "tag": "<a href='https://blahblah.com'>blah</a>" },
*   { "tag": "<a href='https://hahaha.com'>haha</a>" },
*   { "tag": "<a href='https://hohoho.com'>hoho</a>" }
* ]
*/

var values = transformed.values()
/**
* values = [
*   "<a href='http://localhost'>localhost</a>",
*   "<a href='file://documents'>documents</a>",
*   "<a href='https://blahblah.com'>blah</a>",
*   "<a href='https://hahaha.com'>haha</a>",
*   "<a href='https://hohoho.com'>hoho</a>"
* ]
*/

var keys = transformed.keys()
/**
* keys = ["tag", "tag", "tag", "tag", "tag"]
*/


var root = transformed.root()
/**
* root = {
*   "item": {
*     "tag": "<a href='http://localhost'>localhost</a>"
*   },
*   "items": [
*     { "tag": "<a href='file://documents'>documents</a>" },
*     { "tag": "<a href='https://blahblah.com'>blah</a>" },
*   ],
*   "nestedItems": {
*     "childItems": [
*       { "tag": "<a href='https://hahaha.com'>haha</a>" },
*       { "tag": "<a href='https://hohoho.com'>hoho</a>" }
*     ]
*   }
* };
*/

var transformed = ST.select({
  "{{#each items}}": {
    tag: "<a href='{{url}}'>{{text}}</a>"
  }
}).transform({ items: urls });

var root = transformed.root();
/*
root = {
  "item": { "tag": "<a href='http://localhost'>localhost</a>" },
  "items": [
    { "tag": "<a href='file://documents'>documents</a>" },
    { "tag": "<a href='https://blahblah.com'>blah</a>" }
  ],
  "nestedItems": {
    "childItems": {
      "tag": "<a href='http://hahaha.com'>haha</a>"
    },
    "tag": "<a href='https://hohoho.com'>hoho</a>"
  }
}
*/

var keys = transformed.keys();
/*
keys = ["tag", "tag", "tag", "tag", "tag"];
*/

var values = transformed.values();
/*
values = [
  "<a href='http://localhost'>localhost</a>",
  "<a href='file://documents'>documents</a>",
  "<a href='https://blahblah.com'>blah</a>",
  "<a href='http://hahaha.com'>haha</a>",
  "<a href='https://hohoho.com'>hoho</a>"
]
*/

var objects = transformed.objects();
/*
objects = [
  { "tag": "<a href='http://localhost'>localhost</a>" },
  { "tag": "<a href='file://documents'>documents</a>" },
  { "tag": "<a href='https://blahblah.com'>blah</a>" },
  { "tag": "<a href='http://hahaha.com'>haha</a>" },
  { "tag": "<a href='https://hohoho.com'>hoho</a>" }
]
*/

var paths = transformed.paths();

5.6 Sample Projects

5.6.2 JSON as a JSON Query Language

Make complex API queries purely written in JSON.

Since templates in ST are written in JSON, you can pass them around anywhere just like any other data object.

Notice we’re not creating some new query language, it’s just JSON. No convoluted infrastructure to set up!

See the example code at: https://github.com/jasonelle/st.js-jsonql

5.6.4 Routerless Server

Let’s take the router example from right above. Since our router logic is just a JSON (router.json), we don’t even need it on the server side. What if we DON’T keep router.json on the server, but send it from the browser?

Browser

Server

What’s going on here?

We are looking at a server WITHOUT a router. Instead of implementing a router on the server, we send the router itself as part of a network request!

This type of JSON-powered portability provides extreme flexibility when creating interfaces for microservices and RPC endpoints

Also, remember that you can bake validation, conditionals, loops, etc. all in a single JSON IPC/RPC call, which makes it extremely powerful and efficient.

5.6.5 Jasonette

Jasonette uses ja.js to:

    1. Transform an “action” JSON object into a native method call.
    1. Transform any client-side data into a view markup, and then into actual native view components.

Here’s an example where we describe an “action” (function) in a JSON object:

Every function (called “action”) consists of up to four attributes:

    1. "type": Type of action to perform. Jasonette interprets this into an actual native method call.
    1. "options": Arguments to send to the action.
    1. "success": Success callback. You can chain another action here.
    1. "error": Error callback. You can chain another action here.

In above example we make a "$network.request" action call, with a payload of:

This actually translates to a native Objective-c function call, and when it succeeds, it triggers its “success” callback which is another action:

This is where ja.js comes in.

Whenever an action call is made, Jasonette automatically fills the $jason variable with the return value from its preceding action ($network.request) and runs a transform, thereby implementing an actual function call.