JSON Paths, JSON Conditionals, and a Trickle Engine

The topic of paths, conditionals, and rule engines came up recently, so I wanted to put words to some of the approaches I’ve used in the past and topics I’m still reasoning about. I have seen a lot of these concepts applied in whole or in part, and wanted to note my approaches and struggles.

JSON Paths

{a: {b: {c: [ "bar", "baz", "hiccup" ] } }, d: "c"}

.a.b.c.0 would resolve to "bar"
.a.b.{.d}.1 would resolve to "baz"
.a.{.a.b.c.0.0}.{.a.b.c.hiccup.2}.2 would resolve to "hiccup"

The replacement markers can be represented with other tokens instead of {}. But the important part is that the final scope path is resolved in the context of the end point. Objects with keys, arrays with indexes, and strings with offsets down the string. I haven’t reasoned enough about functions as end points, though those would come from the core language as they don’t exist as such in json or in a cross language manner.

These paths allows fairly dynamic interactions that result in known paths through defined matrixes/structures. But that only gets us so far… in addition to this you often need a reasoning engine, with one of the core parts being the logical conditional representations.

Logical Conditionals

The expr in if(expr) was a something I have not fully reached a happiness with. The structure I came up with in the end was a three part array. It could have just as easily be a named structure (object):

[ “path”, “op”, “path”]

In pseudo code, if the first element is equal to the second element, assign the third element to the first:

if ['.a.b.c.0', 'equal', '.a.b.c.1'] then '.a.b.c.0' = '.a.b.c.2'

If only a single path is provided, the truth-y-ness or false-y-ness of that value would be the result. However, with this varying from language to language, just simply assuming the base language’s reasoning is not a viable approach. I’m inclined to adapt Javascript’s model for this. This also means that if you know what your comparisons are, or the engine does, it do can perform a decent amount of caching and collapsed reasoning.

Since everything would resolve to true/false at some level (in this context), the operations of ‘AND’ and ‘OR’ allow nesting to happen. so you could have [ [‘.a.b.c.0’, ‘equal’, ‘.a.b.c.1’], ‘OR’, [‘.a.b.c.1’, ‘equal’, ‘.a.b.c.2’]. Often, I would include a ‘facts structure’ with a value of true at the end of ‘.facts.true’ just to make the conditional statements very clear that everything is path.

It is somewhat clunky, but it is a basic tree structure that provides a quick way to reason about relationships within the document. I used a method to reduce this down to a string, which is then interpreted as code.

Now that we can look things up, and have dynamic look ups, and also can consider the values at the end of various paths, we need some type of rule engine to bring it into a more functional state.

Enter the Trickle Engine

There are lots of ways to approach things in code. Some of them not fully reasoned or encompassing. For me the trickle engine is something I always find as a partial solution, but I have to question the approaches I take to the edge cases… but given:

user = { name:"", city:"", fruit:""}

Consider the following function, as a reaction to someones name being changed:

function name_changed(){
if ( user.name == facts.fred) then user.city = facts.boston
if ( user.city == facts.boston) then user.fruit = facts.apple
}

Here we are describing points of impact (user.city and user.fruit), points of value (facts.city and facts.fruit), conditions ( user.name==, user.city), triggers (user.name), and an implied trickle on first item. So lets turn this into a trickle structure….

[
{ trickle:'user.name', condition:['user.name','equal','facts.fred'], impact:'user.city', value:'facts.boston', trickle:'facts.true'},
{ trickle:'user.city', condition:['user.city','equal','facts.boston'], impact:'user.fruit', value:'facts.apple', trickle:'facts.false'}
]

General concept: When a change occurs, you make that first change outside of the engine. Now you start the engine up, and let things trickle with the following process:

  1. Check each of the triggers, if they match the path of the field that was changed…
  2. Check the conditional, if the conditional is true….
  3. Set the path of impact to the value
  4. If the trickle is true, see step 1 to process ‘children’ rules.
  5. Progress to next sibling trigger, if any
  6. Exit out with a modified structure and perform other tasks

The engine is a separate concept from the paths, but I came around to it after leveraging json paths in a significant manner in various ways through the system. Really the goal is assemble all of your details into a single json structure, prefix your various sections, and let the engine run through actions. The engine requires a depth lock to prevent huge dives into loops (unless that is what you want). Also, you’ll want a rule counter to set some reasonable rounds.

It’s at this point in things, where I started to jump into deeper relationship issues. Direct Infinite Loops [DIL’s], which are rules that call themselves without hope of exit (trigger = impact, impact !in conditions, trickle = true). Indirect Infinite Loops [IIL’s] which also cannot exit at any given step. Blast assignment where a change is triggered from a parent object, and the impact is an array of children requiring expansion (.a.b triggers a change into something like .a.b.c.[*] = “new value”, which assigns the value to every item in the collection.

Lots of these relationships, via paths, can be reasoned about without ever running the actual code. DIL’s do assignment can exit out. UI’s that understand DIL’s, can infer a disabled state of user facing controls…. because it would be silly to know you will always ignore their input, but still suggest you want their input. Never found a decent way of reasoning about IIL’s into something of practical value.

Some level of concurrency and interaction can be extracted from the structures. Dynamic engines can process the code, smarter systems can then cache/compile into more reasonable code. Again, I’ve only had limited exploration of these concepts.

While I’m discovering/reasoning about answers on some of these topics still, I think there is value in a standardized concepts like JSON Path and structured conditionals

JSON Pointer seems to address the path concepts I like. JSON Patch is talking about describing the changes to an object, which I think can help reason about the interactions between rules within the trickle engine. Conditionals recently came up, which I think are best represented in paths instead of embedded values. JSON Schema gives a way to make sure the shape of objects are what you want.