92

For business reasons we need to externalize some conditional logic into external files: preferably JSON.

A simple filter-by scenario could be handled by adding a node as follows:

"filter": [
  {
    "criteria": "status",
    "value": "open",
    "condition": "=="
  }
]

Multiple conditions could be handled by additional values in the array.

"filter": [
  {
    "criteria": "status",
    "value": "open",
    "condition": "=="
  },
  {
    "criteria": "condition2",
    "value": "value2",
    "condition": "=="
  }
]

However, it gets a little confusing when we have handle complex conditions involving ANDs or ORs.

Question: is there a standardized (or even widely accepted) format for representing such logic within JSONs? How would you do it if it were up to you?

NOTE: The first answer has been made an editable wiki so it can be improved by anyone who feels it can be.

Vivek Kodira
  • 2,764
  • 4
  • 31
  • 49
  • 2
    Why can't you use SQL? – Blender Dec 23 '13 at 04:40
  • 3
    :) These are the limitations I have to work with. However, why not simply treat this as an exercise in thought. How would you solve the problem if these were its rules? – Vivek Kodira Dec 23 '13 at 05:38
  • It'd be easier if you explicitly stated the restrictions. Why do you need to use JSON? You can store SQL queries as plain text documents. – Blender Dec 23 '13 at 05:41
  • 2
    Earlier I was more interested in getting the answer to my q & didn't answer the question asked above as a comment satisfactorily. Here is an attempt at answering "Why can't you use SQL?" – Vivek Kodira Aug 01 '14 at 10:04
  • The JSON is used here to abstract logic and make it customize-able. Example: I want to control actions possible on a 'test'. For some customers tests can be updated by anyone (logic: []). In others they can only be updated by the test's owner & if the test's status is 'open'. (Logic: [ { "criteria": "assignee", "value": "", "condition": "==" }, { "criteria": "status", "value": "open", "condition": "==" "value": "$username$", "condition": "!=" } ]) – Vivek Kodira Aug 01 '14 at 10:10

17 Answers17

71

If you must implement this using standard JSON, i'd recommend something akin to Lisp's "S-expressions". A condition could be either a plain object, or an array whose first entry is the logical operation that joins them.

For example:

["AND",
    {"var1" : "value1"},
    ["OR",
        { "var2" : "value2" },
        { "var3" : "value3" }
    ]
]

would represent var1 == value1 AND (var2 == value2 OR var3 == value3).

If you prefer brevity over consistency, you could also allow an object to have multiple properties, which would implicitly be joined by an AND. For example, { "a": "b", "c": "d" } would be equivalent to ["AND", { "a": "b" }, { "c": "d" }]. But there are cases (like the example) where the former syntax can not faithfully represent the condition as written; you'd need additional trickery like translating the condition or using dummy property names. The latter syntax should always work.

cHao
  • 84,970
  • 20
  • 145
  • 172
  • 9
    " Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp." -- Greenspun's tenth rule of programming – D-side Oct 20 '15 at 20:50
  • How this JSON will represent condition: var1 == value1 AND (var2 == value2 OR var3 == value3) AND (var4 == value4) – user3089214 Nov 19 '16 at 04:36
  • @user3089214: Simply append a `{ "var4" : "value4" }` to the array that starts with `"AND"`. You end up with `["AND", {"var1" : "value1"}, ["OR", {"var2" : "value2"}, {"var3": "value3"}], {"var4" : "value4"}]`. Or, if you want only binary operations, you can say `["AND", ["AND", {"var1" : `...`: "value3" } ] ], { "var4" : "value4" } ]`. Up to you, but i consider the latter not worth the hassle. – cHao Nov 19 '16 at 16:43
  • How would json look for ``var1 == value1 AND(var2 = value2 OR(var3 = value3 OR var4 = value4)) OR var5 = value5 OR var6 = value6`` – Gazeciarz Sep 21 '17 at 11:20
  • @Gazeciarz: I can't justify whipping up JSON for every combination people come up with. :) I will say, if you read up about abstract syntax trees, the structure you'll need should be a bit clearer. (The structures i'm talking about are just a kind of simplified AST.) Or mess with Lisp for a while. :) – cHao Sep 21 '17 at 11:39
  • I am just thinking if this structure for sure may represent any query I will try to build. I spend few hours on thinking about this solution and looks great for me. I think it will until logical expression will be reduced to most simplified one. – Gazeciarz Sep 22 '17 at 08:31
  • @Gazeciarz: It can represent any query that involves testing properties for equality with constant values. You just need to define the operators (the example assumes definitions for "AND" and "OR"). If you want more complex comparisons (say you want to support `<`, or you want to compare against calculated values), you'll need something closer to [Jeremy's answer](https://stackoverflow.com/a/32975160/319403) (though you will still need to extend it a bit). – cHao Sep 22 '17 at 17:48
  • Thanks alot for idea. Works fine in java aswell. Unfortunately I did not any library which will do the job (serialization/deserialization and testing the rules base on input) so I implemented everything from scratches. Pretty cool :) – Gazeciarz Sep 25 '17 at 09:59
  • @Gazeciarz could you help on how you are using above format in java to validate or create object – Yogesh Mar 03 '20 at 13:53
  • Can anyone help on how can I use this json format for kotlin code expressions – Himanshu Oct 13 '20 at 09:14
69

I needed a format that would:

  1. Support comparisons other than equality.
  2. Let variables appear in any position, not just be compared to literals.
  3. Be consistent, terse, secure, and extensible.

So I built up a format I'm calling JsonLogic. A rule is a JSON object, with the operator in the key position, and one or an array of arguments in the value position. (Inspired by Amazon CloudFormation functions.) Any argument can be another rule, so you can build arbitrarily deep logic.

I've also written two parsers for it: JsonLogic for JavaScript and JsonLogic for PHP.

cHao's example would be written as

{ "and", [
    {"==", [ {"var" : "var1"}, "value1" ]},
    { "or", [
        {"==", [ {"var" : "var2"}, "value2" ]},
        {"==", [ {"var" : "var3"}, "value3" ]}
    ]}
]}

var here is the operator to get a property of the "data" object, passed along with the "rule" object to the parser, e.g.:

jsonLogic(
    {"==", [{"var":"filling"}, "apple"]}    // rule, is this pie apple?
    {"filling":"apple", "temperature":100}  // data, a pie I'm inspecting
);
// true

There are lots more possible operators (greater than, not-equals, in-array, ternary, etc) and both parsers are available on GitHub (with unit tests and documentation).

Jeremy Wadhams
  • 1,744
  • 18
  • 26
  • Your library looks really impressive at first glance. I'm going to need something akin in an API I'm building. Is there really no established standard for expressing conditional logic as JSON? – Eric H. Nov 16 '16 at 23:54
  • @Eric:. No, there isn't. Expressing conditional logic in textual form is why programming and query languages exist. :) JSON was not designed to serve as either. The people who need to do this are rare enough and have varied enough needs that no one schema would gain traction, let alone work for everyone without getting unwieldy for simple cases. – cHao Nov 19 '16 at 17:16
  • @Jeremy I need some quick info here, I am impressed by the jsonLogic standard, but visual representation of this logic ( in the form of UI ) is a pain. First convert it to a Tree and then convert it back to jsonLogic format. Do you think this is the only way to go if we need a visual representation. Example here :- https://ukrbublik.github.io/react-awesome-query-builder . This example takes a Tree Json ( found in the example ) and converts it into jsonLogic just because visual representation needs id's , children:[] in tree json to iterate over and convert to tiles with dropdowns etc. – Veryon890 Oct 06 '20 at 11:48
  • @veyron890 I wasn't aware of that builder, that's pretty great. We've also had some luck at Dealer Inspire building extremely custom GUIs that can build a limited list of domain specific json logic statements (e.g. if you're building a tax it takes a float tax rate and some check boxes for what it applies to and turns it into * and var statements). I'm not aware of any general purpose solution that I'd want to write more than toy programs in. – Jeremy Wadhams Oct 13 '20 at 15:01
  • @JeremyWadhams JsonLogic is wonderful. Please, begin active maintenance on the library again. I have multiple projects with intent to use it, but I'm worried about it's lack of active development. – WebWanderer Nov 29 '21 at 20:53
9

By the way, IBM DB2 supports logic statements encoded in JSON.

Boolean operations look like a cross between cHao's solution and Amazon CloudFormation:

{"$and":[{"age":5},{"name":"Joe"}]}

Comparison operations look, to me, like transliterated SQL. (Instead of Amazon or Russellg or cHao's movement toward an abstract syntax tree.)

{"age":{"$lt":3}}
gazdagergo
  • 6,187
  • 1
  • 31
  • 45
Jeremy Wadhams
  • 1,744
  • 18
  • 26
6

I had a similar need (to build up a sql where clause in javascript). I create dthe following javascript function:

  function parseQuery(queryOperation){
        var query="";
        if (queryOperation.operator == 'and')
            query = "(" + parseQuery(queryOperation.leftOp) + ") AND (" + parseQuery(queryOperation.rightOp) + ")";
        if (queryOperation.operator == 'or')
            query = "(" + parseQuery(queryOperation.leftOp) + ") OR (" + parseQuery(queryOperation.rightOp) + ")";
        if (queryOperation.operator == '=')
            query = "(" + queryOperation.leftOp +" = "+ queryOperation.rightOp + ")";
        return query;
    }

I create my queryOperation Like this:

 var queryObject =             {          
            operator: 'and',
            leftOp: {
                leftOp: 'tradedate',
                operator: '=',
                rightOp: new Date()
            },
            rightOp: {
                operator: 'or',
                leftOp: {
                    leftOp: 'systemid',
                    operator: '=',
                    rightOp: 9
                },
                rightOp: {
                    leftOp: 'systemid',
                    operator: '=',
                    rightOp:10
                }
            }
        };

When I pass my queryOperation to ParseQuery it returns ((tradedate= Thu Jul 24 17:30:37 EDT 2014)) AND (((systemid= 9)) OR ((systemid= 10)))

I need to add some type conversions and other operators, but the basic structure works.

RussGove
  • 322
  • 5
  • 18
6

I came up with this format with the primary goal of reading as close as possible to actually SQL.

Here's the Type def in typescript:

type LogicalOperator = 'AND' | 'OR';
type Operator = '=' | '<=' | '>=' | '>' | '<' | 'LIKE' | 'IN' | 'NOT IN';
type ConditionParams = {field: string, opp: Operator, val: string | number | boolean};
type Conditions = ConditionParams | LogicalOperator | ConditionsList;
interface ConditionsList extends Array<Conditions> { }

Or BNF (ish? my cs teachers wouldn't be proud)

WHEREGROUP: = [ CONDITION | ('AND'|'OR') | WHEREGROUP ]
CONDITION: = {field, opp, val}

With the following Parsing Rules:

  1. AND is optional (I typically add it for readability). If logical LogicalOperator is left out between conditions, it will automatically joins them with AND
  2. Inner arrays are parsed as nested groups (EG get wrapped in ())
  3. this type does not restrict multiple logical operators consecutively (unfortunately). I handled this by just using the last one, although I could have thrown a runtime error instead.

Here are some examples (typescript playground link):

1 AND 2 (AND inferred)

[
    { field: 'name', opp: '=', val: '123' },
    { field: 'otherfield', opp: '>=', val: 123 }
]

1 OR 2

[
    { field: 'name', opp: '=', val: '123' },
    'OR',
    { field: 'annualRevenue', opp: '>=', val: 123 }
]

(1 OR 2) AND (3 OR 4)

[
    [
        { field: 'name', opp: '=', val: '123' },
        'OR',
        { field: 'name', opp: '=', val: '456' }
    ],
    'AND',
    [
        { field: 'annualRevenue', opp: '>=', val: 123 },
        'OR',
        { field: 'active', opp: '=', val: true }
    ]
]

1 AND (2 OR 3)

[
    { field: 'name', opp: '=', val: '123' },
    'AND',
    [
        { field: 'annualRevenue', opp: '>=', val: 123 },
        'OR',
        { field: 'active', opp: '=', val: true }
    ]
]

1 AND 2 OR 3

[
    { field: 'name', opp: '=', val: '123' },
    'AND',
    { field: 'annualRevenue', opp: '>=', val: 123 },
    'OR',
    { field: 'active', opp: '=', val: true }
]

1 OR (2 AND (3 OR 4))

[
    { field: 'name', opp: '=', val: '123' },
    'OR',
    [
        { field: 'annualRevenue', opp: '>=', val: 123 },
        'AND',
        [
            { field: 'active', opp: '=', val: true },
            'OR',
            { field: 'accountSource', opp: '=', val: 'web' }
        ]
    ]
]

As you can see, if you were to remove , and property names, then just replace the [] with (), you'd basically have the condition in SQL format

NSjonas
  • 10,693
  • 9
  • 66
  • 92
  • also worth note... I tried to use typescript tuple `[string, Operator, string|number|boolean]` instead of the object (to further increase readability, but I couldn't get the type to work correctly (might be a bug with ts). – NSjonas Nov 08 '18 at 20:00
  • 2
    You mayy be interested in http://jsonlogic.com/ which appears to take a very similar approach . – Kwame Feb 07 '19 at 19:21
  • Why did pick only even combinations why not odd? how about `1 AND 2 OR 3` vs `1 AND (2 OR 3)`? – user1870400 Aug 13 '19 at 10:24
  • @user1870400 that's just how I happened to set up the examples. This format handles odd number of conditions... Added an example showing so – NSjonas Aug 13 '19 at 15:12
  • What if there is no parenthesis? Simply say 1 And 2 OR 3? – user1870400 Aug 13 '19 at 15:19
  • @user1870400 then just put them all in the first array... You can add as many conditions as you want to any group. Examples updated – NSjonas Aug 13 '19 at 22:27
  • 1
    @NSjonas i would say this is probably the best and most readable structure, and I just wrote a function to parse this conditions Array. – Karthik Dec 21 '19 at 09:15
3

My colleague suggested this possible solution:

"all OR conditions would be an array while AND conditions would be objects,

For example,OR can match any of the objects in the array:

[
  {
    "var1":"value1"
  },
  {
    "var2":"value2"
  },
  {
    "var3":"value3"
  }
]

AND would be

{ 
  "var1":"val1",
  "var2":"val2",
  "var3":"val3"
}
vamsiampolu
  • 6,328
  • 19
  • 82
  • 183
Vivek Kodira
  • 2,764
  • 4
  • 31
  • 49
  • 1
    And how would `var1 == value1 AND (var2 == value2 OR var3 == value3)` be represented? Only way i see is by translating it to `(var1 == value1 AND var2 == value2) OR (var1 == value1 AND var3 == value3)`...which, eh. – cHao Dec 23 '13 at 04:41
  • 1
    :) The answer is not complete. That is why I made it a community editable wiki - so we could all contribute and come up with an acceptable/working solution – Vivek Kodira Dec 23 '13 at 05:36
  • Another alternative would be to use no-sql search syntaxes to represent such logic. In MongoDB: SELECT * FROM users WHERE status = "A" AND age = 50 becomes db.users.find({ status: "A", age: 50 }) and SELECT * FROM users WHERE status = "A" OR age = 50 becomes db.users.find({ $or: [ { status: "A" } , { age: 50 } ] } reference http://docs.mongodb.org/manual/reference/sql-comparison/ – Vivek Kodira Feb 11 '14 at 12:53
2

Please check out (JSL)[https://www.npmjs.com/package/lib-jsl ]. It seems to fit the description given.

Here is a sample :

var JSL = require('lib-jsl');

var bugs = [
    [{ bug : { desc: 'this is bug1 open', status : 'open' } }],
    [{ bug : { desc: 'this is bug2 resolved', status : 'resolved' } }],
    [{ bug : { desc: 'this is bug3 closed' , status : 'closed' } }],
    [{ bug : { desc: 'this is bug4 open', status : 'open' } }],
    [{ bug : { desc: 'this is bug5 resolved', status : 'resolved' } }],
    [{ bug : { desc: 'this is bug6 open', status : 'open' } }],

    [   { workInProgress : '$bug'},
        { bug : '$bug'},
        { $or : [
            { $bind : [ '$bug', { status : 'open'} ] },
            { $bind : [ '$bug', { status : 'resolved'} ] }
        ] }
    ]
];
var query = [{workInProgress : '$wip'}]
var transform = '$wip'
var jsl = new JSL ({
    rules : bugs,
    query : query,
    transform : transform
});
var retval = jsl.run();
console.log(JSON.stringify(retval, null,2));

The response is :

[
  {
    "desc": "this is bug1 open",
    "status": "open"
  },
  {
    "desc": "this is bug2 resolved",
    "status": "resolved"
  },
  {
    "desc": "this is bug4 open",
    "status": "open"
  },
  {
    "desc": "this is bug5 resolved",
    "status": "resolved"
  },
  {
    "desc": "this is bug6 open",
    "status": "open"
  }
]

The main work is done by the query defined in the rule workInProgress :

[   { workInProgress : '$bug'},
    { bug : '$bug'},
    { $or : [
        { $bind : [ '$bug', { status : 'open'} ] },
        { $bind : [ '$bug', { status : 'resolved'} ] }
    ] }
]

This rule can be read as :

To satisfy the query with workInProgress, we define a variable {workInProgress : '$bug'}, which we then proceed to match against all bugs in the database using the next part of the rule {bug : '$bug'}. This part matches all bugs since the shape of the object (it's keys: 'bug') matches the bug records in the database. The rule further asks the $bug variable to be $bind(ed) against patterns containing relevant status values (open and closed) within a $or. Only those bug records whose status value in $bug satisfies all parts of the rule's body qualify for the result.

The result is finally transformed using the transform specification : transform : '$wip' which literally asks for an array of all values returned in the $wip variable of the query.

  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes – Smita Ahinave May 12 '16 at 04:33
  • Thanks @ruchir-walia. In structure, it looks similar to the accepted answer. The link you've referenced is not working though. – Vivek Kodira May 12 '16 at 09:49
  • @VivekKodira thank you, please try the link once again. It has been corrected. – Ruchir Walia May 12 '16 at 10:18
2

Following Jeremy Wadhams comment, I implemented a parser I hope it can help you:

https://play.golang.org/p/QV0FQLrTlyo

The idea is to set all logic operators in special keys with $ character like $and or $lte.

As an example:

{ 
   "$or":[ 
      { 
         "age":{ 
            "$lte":3
         }
      },
      { 
         "name":"Joe"
      },
      { 
         "$and":[ 
            { 
               "age":5
            },
            { 
               "age ":{ 
                  " $nin ":[ 
                     1,
                     2,
                     3
                  ]
               }
            }
         ]
      }
   ]
}

Regards.

Is translated as:

 ( age  <= 3 OR  name  = Joe  OR  ( age  = 5  AND  age  NOT IN (1,2,3) )  )  
omotto
  • 1,721
  • 19
  • 20
1

Formula parser + a bit of JS codes to put data into formulas, is another solution described with example in this answer.

Developia
  • 3,928
  • 1
  • 28
  • 43
1

We created an npm package json-conditions to handle this. It's not as full featured as some of the others here, but it's easy to translate into a simple UI for non-technically savvy clients as complex rules are possible without nesting and it covers virtually all use cases they can come up with.

Example on runkit

const objectToTest = {
  toy: {
    engines: 1,
  },
  batteries: 'AA',
  fun: true,
};

const simpleRules = [
    // required: true means This first condition must always be satisfied
    { property: 'fun', op: 'eq', value: true, required: true },
    { property: 'toy.engines', op: 'gt', value: 2 },
    { property: 'batteries', op: 'present' },
];

// Returns true
checkConditions({
    rules: simpleRules,
    satisfy: 'ANY', // or ALL to require all conditions to pass
    log: console.log,
}, objectToTest);
ChrisJ
  • 2,486
  • 21
  • 40
1

Following Jeremy Wadhams comment, I mapped the json by MongoDB logical query operator and MongoDB comparison query operator but MongoDB don't allow $ character in content:

{"and":[
   {"age":{"eq": 5}},
   {"name":{"eq": "Joe"}
]}
Wendel
  • 2,809
  • 29
  • 28
0

Logic can be implemented with "logicOp": "Operator" on a "set": ["a","b" ...] For cHau's example:

"var": {
         "logicOp": "And",
         "set": ["value1",
                 {
                    "LogicOp": "Or",
                    "set": ["value2", "value3"]
                 }
              ]
       }

There can also be other attributes/operations for the set for example

"val": { "operators": ["min": 0, "max": 2], "set": ["a", "b", "c"] } 

For a sundae with two scoops of one or more icecream types, 1 toppings and whipcream

"sundae": {
            "icecream": { 
                          "operators": [ "num": 2,
                                        "multipleOfSingleItem": "true"],
                          "set": ["chocolate", "strawberry", "vanilla"]
                        },
            "topping": {
                          "operators": ["num": 1],
                          "set": ["fudge", "caramel"]
                       },
            "whipcream": "true"
          }
Rhubbarb
  • 4,248
  • 6
  • 36
  • 40
JayS
  • 2,057
  • 24
  • 16
0

I just wanted to help by defining a parsing logic in JavaScript for the JSON structure mentioned in the answer: https://stackoverflow.com/a/53215240/6908656

This would be helpful for people having a tough time in writing a parsing logic for this.

evaluateBooleanArray = (arr, evaluated = true) => {
    if (arr.length === 0) return evaluated;
    else if (typeof arr[0] === "object" && !Array.isArray(arr[0])) {
      let newEvaluated = checkForCondition(arr[0]);
      return evaluateBooleanArray(arr.splice(1), newEvaluated);
    } else if (typeof arr[0] === "string" && arr[0].toLowerCase() === "or") {
      return evaluated || evaluateBooleanArray(arr.splice(1), evaluated);
    } else if (typeof arr[0] === "string" && arr[0].toLowerCase() === "and") {
      return evaluated && evaluateBooleanArray(arr.splice(1), evaluated);
    } else if (Array.isArray(arr[0])) {
      let arrToValuate = [].concat(arr[0]);
      return evaluateBooleanArray(
        arr.splice(1),
        evaluateBooleanArray(arrToValuate, evaluated) 
      );
    } else {
      throw new Error("Invalid Expression in Conditions");
    }
  };

So the param arr here would be an array of conditions defined in the format as described by the attached link.

Karthik
  • 629
  • 2
  • 8
  • 12
0

The first came to mind would be the recurisve

dict1={'$lte':'<','$nin':'not in '}

def updateOp(subdictItem):

    for ites in subdictItem:
        ops = ites
        print dict1.get(ops), subdictItem.get(ops), type(subdictItem.get(ops))
        if type(subdictItem.get(ops)) is list:
            valuelist=subdictItem.get(ops)
            strlist=' ,'.join([str(x) for x in valuelist])
            sub = dict1.get(ops) + "(" +strlist +")"
        else:
            sub = dict1.get(ops) +' ' + str(subdictItem.get(ops))
    return sub

def jsonString(input_dict):
    items=''
    itemslist=[]
    list = []
    for item in input_dict:
        op=item
        itemssublist=[]

        # print "item",op
        for subitem in input_dict.get(op):
            # print("subitem",subitem)
            for ite in subitem:
                if ite not in ('and','or'):
                    # print('ite_raw',ite,subitem.get(ite))
                    sub=''
                    if type(subitem.get(ite)) is dict:
                        sub=updateOp(subitem.get(ite))
                    else:
                        sub='=' + str(subitem.get(ite))
                    itemssublist.append(ite+sub)
                else:
                    item1=jsonString(subitem)
                    itemssublist.append(item1)

        delimiter=" "+op+ " "
        items= "("+delimiter.join(itemssublist)+")"
    return items 





if __name__ == "__main__":

    input_dict={}
    with open('ops.json','r') as f:
        input_dict=json.load(f)

    print input_dict

    test= jsonString(input_dict)

#result : (age< 3 or name=Joe or (age=5 and age not in (1 ,2 ,3)))
ops.json file:
{
   "or":[
      {
         "age":{
            "$lte":3
         }
      },
      {
         "name":"Joe"
      },
      {
         "and":[
            {
               "age":5
            },
            {
               "age ":{
                  "$nin":[
                     1,
                     2,
                     3
                  ]
               }
            }
         ]
      }
   ]
}
Emma Y
  • 555
  • 1
  • 9
  • 16
0

Use arrays, alternate between OR and AND conditions:

const rule0 = [
    [ "ruleA1", "ruleA2", "ruleA3" ],
    [ "ruleB5", "ruleB6" ],
    [ "ruleB7" ]
];

const rule1 =  [
    [ "ruleA1", "ruleA2", [ [ "ruleA3A" ], [ "ruleA3B1", "ruleA31B2" ] ] ],
    [ "ruleB5", "ruleB6" ],
    [ "ruleC7" ]
];
function ruler (rules) {
    return "( " +
        rules.map(or_rule =>
            "( " +
            or_rule.map(and_rule =>
                Array.isArray(and_rule) ? ruler(and_rule) : and_rule
            ).join(" AND ") +
            " )"
        ).join(" OR ") + " )";
}

Output:

ruler(rule0)
'( ( ruleA1 AND ruleA2 AND ruleA3 ) OR ( ruleB5 AND ruleB6 ) OR ( ruleB7 ) )'

ruler(rule1)
'( ( ruleA1 AND ruleA2 AND ( ( ruleA3A ) OR ( ruleA3B1 AND ruleA31B2 ) ) ) OR ( ruleB5 AND ruleB6 ) OR ( ruleC7 ) )'
0

I created a structure thinking about a sequential iteration:

[ 
    {
       "operator": null,
       "field": "age",
       "condition": "eq",
       "value": 5
    },
    {
       "operator": "and",
       "field": "name",
       "condition": "eq",
       "value": "Joe"
    }
]
Wendel
  • 2,809
  • 29
  • 28
0

Want to throw my own hat in the ring here.

The best serializable predicate you can use to represent logic in JSON format has already largely won the day. It’s called JSON Schema (yes, a schema is a predicate, data will either validate against it or it won’t - true/false - a predicate)

Why do I say it’s win the day? We use it everywhere all the time, open API/swagger, embedded in our IDEs autocompletes, in various dev documentation websites

All you have to do is wrap some logic around how to resolve your data and feed it to a json schema validator with a json schema

https://www.npmjs.com/package/json-schema-rules-engine

Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100