Portable Rules and Arithmetic with JsonLogic

The task is to create a form with fields that perform arithmetic operations. The formula and the form fields are completely freeform, almost like a spreadsheet. And because that's easy enough, make it portable so that the formula works in different environments like a mobile app and a web app.

Arithmethic Formulas and JsonLogic

It's no problem to find libraries in every programming language, that parse math expressions from a string like 1 + 1 * ( foo - bar ).

But there are quirks when variables are involved and I had a specific case that lead me to take a closer look into JsonLogic, despite the criticism of being an unreadable language construct and that's what programming languages are for.

From the JsonLogic website:

If you're looking for a way to share logic between front-end and back-end code, and even store it in a database, JsonLogic might be a fit for you.

JsonLogic isn't a full programming language. It's a small, safe way to delegate one decision. You could store a rule in a database to decide later. You could send that rule from back-end to front-end so the decision is made immediately from user input. Because the rule is data, you can even build it dynamically from user actions or GUI input.

The most simple example of JsonLogic is the following:


// {"operator" : ["values" ... ]}
jsonLogic.apply( { "==" : [1, 1] } );
// true

it evaluates to 1 == 1.

Check out the JsonLogic docs.

My Use Case

The Formio.js form builder lets you write complex JavaScript to validate fields or do complex calculations with values from other fields.

This is the most simple example:

and it's setup like this:

For one use JavaScript or JsonLogic (the latter overwrites the JavaScript in this case)

So instead of writing a formula like this

value = ( data.label1 + data.label2 ) * data.select

it's going to be like this:

{
  "*": [
  {
    "+": [
    {"var": "data.label1"},
    {"var": "data.label2"}
    ]
  },
  {"var": "data.select"}
  ]
}

Does this look like a nightmare to let the user write raw JsonLogic? It sure does! Do I want to write a language parser that interprets the JavaScript from the form builder and translates it in a math expression like (label1+label2) * select? Not sure I should do that either.1

As far as I understand, every math expression parser turns the expression string into an AST (Abstract syntax tree) and that's basically what JsonLogic represents and gets criticized for.

Also, what if the user puts in garbage data, can a math library deal with that?

JsonLogic rules seem to be a good compromise especially since there are implementations in many programming languages.

In my case, I can use those rules in JavaScript and PHP.

And for the user who has to write those rules? Do they have to learn the syntax as they had to learn Excel functions?

There is a JavaScript to JSON rules converter, which might help, and a visual rule builder, which looks good but doesn't really help understand JsonLogic better.

For what its worth, I put together a demo, that converts simple JavaScript calculations into JsonLogic and also outputs the result from math expressions and JsonLogic.

Conclusion

I'm honestly not a 100% sure what's the best choice. Here are some thoughts:

Math expressions

  • are easy to write
  • it's just a text string to store
  • limits the user to math rules
  • harder to build a GUI around it

JsonLogic

  • not easy to write
  • but easy to store
  • and easy to use programmatically
  • easier to build a GUI around it

As for my use case, I wrote a Formio.js component that takes in a plain math expression like above and wraps it in a math.js function, utilizing Formio.js calculateValue JavaScript field and executes it in the background.

value = math.evaluate(component.formula, data);

After some back and forth, this might be the best solution for now.

The Demo was made with vuejs/petite-vue: 5kb subset of Vue optimized for progressive enhancement.

Criticism

Math Libraries

Unrelated but interesting


  1. This is a simple example. Give a user the field to write JavaScript in and it will be used for good and for bad.