ST

Select Transform

JSON Template over JSON
GithubTryTwitter


Features

1. Select

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

Step 1. Take any JSON object

var data = {
  "links": [
    { "remote_url": "http://localhost" },
    { "file_url": "file://documents" },
    { "remote_url": "https://blahblah.com" }
  ],
  "preview": "https://image",
  "metadata": "This is a link collection"
}

Step 2. Find all key/value pairs that match a selector function

var sel = ST.select(data, function(key, val) {
  return /https?:/.test(val);
})

Step 3. Once selected, you can make queries.

var keys = sel.keys();
//  [
//    "remote_url",
//    "remote_url",
//    "preview"
//  ]

var values = sel.values();
//  [
//    "http://localhost",
//    "https://blahblah.com",
//    "https://image"
//  ]

var paths = sel.paths();
//  [
//    "[\"links\"]",
//    "[\"links\"]",
//    ""
//  ]

Learn more about Select

2. Transform

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

Step 1. Take any JSON object

var data = {
  "title": "List of websites",
  "description": "This is a list of popular websites"
  "data": {
    "sites": [{
      "name": "Google",
      "url": "https://google.com"
    }, {
      "name": "Facebook",
      "url": "https://facebook.com"
    }, {
      "name": "Twitter",
      "url": "https://twitter.com"
    }, {
      "name": "Github",
      "url": "https://github.com"
    }]
  }
}

Step 2. Select and transform with a template JSON object

var sel = ST.select(data, function(key, val){
            return key === 'sites';
          })
          .transformWith({
            "items": {
              "{{#each sites}}": {
                "tag": "<a href='{{url}}'>{{name}}</a>"
              }
            }
          })

Step 3. Get the result

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

var values = sel.values();
//  [
//    "<a href='https://google.com'>Google</a>",
//    "<a href='https://facebook.com'>Facebook</a>",
//    "<a href='https://twitter.com'>Twitter</a>",
//    "<a href='https://github.com'>Github</a>"
//  ]

var objects = sel.objects();
//  [
//    {
//      "tag": "<a href='https://google.com'>Google</a>"
//    }, {
//      "tag": "<a href='https://facebook.com'>Facebook</a>"
//    }, {
//      "tag": "<a href='https://twitter.com'>Twitter</a>"
//    }, {
//      "tag": "<a href='https://github.com'>Github</a>"
//    }
//  ]

var root = sel.root();
//  {
//    "items": [{
//      "tag": "<a href='https://google.com'>Google</a>"
//    }, {
//      "tag": "<a href='https://facebook.com'>Facebook</a>"
//    }, {
//      "tag": "<a href='https://twitter.com'>Twitter</a>"
//    }, {
//      "tag": "<a href='https://github.com'>Github</a>"
//    }]
//  }

Learn more about Transform


How Does It Work?

  1. st.js is a library that adds a couple of powerful methods to JavaScript's native JSON.
  2. So you can simply use it with the syntax ST.select(...).transform(...)
  3. The library is just a single file, made up of stateless functions, with NO dependency.
  4. 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

1. Declarative JSON API Template

Build JSON using a simple, human-readable, and declarative template instead of manually coding it.
Visit Github

Old way: Manually construct object

// app.js
app.get('/', function (req, res) {
  var response = {}
  response["current_user"] = {
    username: "@" + req.user.username,
    firstname: req.user.name.split(' ')[0],
    lastname: req.user.name.split(' ')[1]
  }
  var transformed_posts = db.posts.map(function(post){
    return {
      slug: post.slug
      permalink: "https://blahblahblah.blahblah/" + post.slug,
      post_title: post.title,
      post_content: post.content
    }
  })
  response["posts"] = transformed_posts
  res.json(response)
})

New way: Declarative approach with st.js

// app.js
app.get('/', function (req, res) {
  res.json(ST.select(require('./template.json'))
               .transform({user: req.user, posts: db.posts})
               .root())
})

// template.json
{
  "current_user": {
    "username": "@{{user.username}}",
    "firstname": "{{user.name.split(' ')[0]}}",
    "lastname": "{{user.name.split(' ')[1]}}"
  },
  "posts": {
    "{{#each posts}}": {
      "slug": "{{slug}}",
      "permalink": "https://blahblahblah.blahblah/{{slug}}",
      "post_title": "{{title}}",
      "post_content": "{{content}}"
    }
  }
}

2. Parse JSON like never before

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

3. 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!
Visit Github

4. App as Data

Templates are normally used for views
But the cool thing about JSON is it can be used to declaratively represent ANYTHING from Model to View to Controller.

What if we set executable functions as leaf nodes of an object, select & transform it, and then auto-trigger the resolved function?
We have built a router in JSON!

Basically, the entire router logic is represented as a piece of data.

Browser

var rpc = {
  name: "add",
  args: [2,3,1]
}
fetch("http://localhost:3000", {
  method: "POST",
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(rpc)
).then(function(res) {
  console.log(res.json());
})

Server

// router.json
[{
  "{{#if 'name' in this}}": [{
    "{{#if name === 'add'}}": 'add_service'
  }, {
    "{{#elseif name === 'subtract'}}": [{
      "{{#if args.length === 2}}": 'subtract_service'
    }, {
      "{{#else}}": 'error_service'
    }]
  }]
}, {
  "{{#else}}": 'error_service'
}]

// express server
app.post('/',  (req, res) => {
  const Services = {
    add_service: function(){
        return Array.prototype.slice
                    .call(arguments)
                    .reduce((a,b) => {
                      return a+b;
                    }, 0)
    },
    subtract_service: function() {
      return arguments[0] - arguments[1]
    },
    error_service: function() {
      return 'error';
    }
  }
  const name = ST.transform(require('./router.json'), req.body);
  res.json(Services[name].apply(this, req.body.args));
});

5. 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

var router = [{
  "{{#if 'name' in this}}": [{
    "{{#if name === 'add'}}": 'add_service'
  }, {
    "{{#elseif name === 'subtract'}}": [{
      "{{#if args.length === 2}}": 'subtract_service'
    }, {
      "{{#else}}": 'error_service'
    }]
  }]
}, {
  "{{#else}}": 'error_service'
}];

var rpc = {
  name: "add",
  args: [2,3,1],
  router: router
}

fetch("http://localhost:3000", {
  method: "POST",
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(rpc)
).then(function(res) {
  console.log(res.json());
})

Server

// express server
app.post('/',  (req, res) => {
  const Services = {
    add_service: function(){
        return Array.prototype.slice
                    .call(arguments)
                    .reduce((a,b) => {
                      return a+b;
                    }, 0)
    },
    subtract_service: function() {
      return arguments[0] - arguments[1]
    },
    error_service: function() {
      return 'error';
    }
  }
  const name = ST.transform(req.body.router, req.body);
  res.json(Services[name].apply(this, req.body.args));
});
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.

6. Build your OWN Turing complete JSON markup language!

If you read this far, you may feel like you could implement practically any programming concept in a declarative manner, using JSON.
This is because ST.js is a low level building block for creating a Turing Complete JSON markup language

st.js is the core JSON parser that powers Jasonette, a framework that lets you build native iOS/Android apps by writing nothing but a JSON markup.
Check out Jasonette

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

Learn more about how Jasonette implements programming concepts by reading this blog post, and build your own using ST.js!

How to use

In a browser

<script src="st.js"></script>
<script>
var parsed = ST.select({ "items": [1,2,3,4] })
                .transformWith({
                  "{{#each items}}": {
                    "type": "label", "text": "{{this}}"
                  }
                })
                .root();
</script>

In node.js

Install through npm:

$ npm install stjs

Use

const ST = require('st');

const parsed = ST.select({ "items": [1,2,3,4] })
                .transformWith({
                  "{{#each items}}": {
                    "type": "label", "text": "{{this}}"
                  }
                })
                .root();