将逻辑表示为JSON中的数据

时间:2013-12-23 04:36:33

标签: json algorithm

出于商业原因,我们需要将一些条件逻辑外部化为外部文件:最好是JSON。

可以通过添加节点来处理简单的过滤方案:

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

多个条件可以通过数组中的其他值来处理。

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

然而,当我们处理涉及AND或OR的复杂条件时,它会有点混乱。

问题:在JSON中表示这种逻辑是否有标准化(甚至被广泛接受)的格式?如果由你决定,你会怎么做?

注意:第一个答案是一个可编辑的维基,所以任何感觉都可以改进。

12 个答案:

答案 0 :(得分:48)

如果必须使用标准JSON实现此功能,我建议使用类似于Lisp的“S表达式”。条件可以是普通对象,也可以是第一个条目是连接它们的逻辑操作的数组。

例如:

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

代表var1 == value1 AND (var2 == value2 OR var3 == value3)

如果您更喜欢简洁而不是一致性,您还可以允许对象具有多个属性,这些属性将隐式地通过AND连接。例如,{ "a": "b", "c": "d" }等同于["AND", { "a": "b" }, { "c": "d" }]。但是有些情况(比如示例),前一种语法不能忠实地表示写入的条件;你需要额外的技巧,比如翻译条件或使用虚拟属性名称。后一种语法应始终有效。

答案 1 :(得分:28)

我需要一种格式:

  1. 支持除相等之外的比较。
  2. 让变量出现在任何位置,而不仅仅是与文字进行比较。
  3. 保持一致,简洁,安全和可扩展。
  4. 所以我建立了一种我打电话给JsonLogic的格式。规则是JSON对象,操作符位于键位置,值位置中有一个或一组参数。 (灵感来自Amazon CloudFormation functions。)任何参数都可以是另一条规则,因此您可以构建任意深度的逻辑。

    我还为它编写了两个解析器:JsonLogic for JavaScriptJsonLogic for PHP

    cHao的例子将写成

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

    var这里是获取"数据"的属性的运算符。对象,与"规则一起传递"解析器的对象,例如:

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

    还有更多可能的运算符(大于,不等于,数组内,三元等),并且两个解析器都可以在GitHub上使用(带有单元测试和文档)。

答案 2 :(得分:4)

我有类似的需求(在javascript中构建一个sql where子句)。我创建了以下javascript函数:

  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;
    }

我创建了queryOperation,就像这样:

 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
                }
            }
        };

当我将queryOperation传递给ParseQuery时,它返回 ((tradedate = Thu Jul 24 17:30:37 EDT 2014))AND(((systemid = 9))OR((systemid = 10)))

我需要添加一些类型转换和其他运算符,但基本结构可以工作。

答案 3 :(得分:4)

顺便说一句,IBM DB2 supports logic statements encoded in JSON

布尔运算看起来像cHao的解决方案和Amazon CloudFormation之间的交叉:

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

对我而言,比较操作看起来就像音译SQL一样。 (而不是亚马逊或Russellg或cHao向抽象语法树的转变。)

{"age":{"$lt":3}}

答案 4 :(得分:2)

我的同事提出了这个可能的解决方案:

“所有OR条件都是数组,AND条件是对象,

例如,OR可以匹配数组中的任何对象:

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

将是

{ 
  "var1":"val1",
  "var2":"val2",
  "var3":"val3"
}

答案 5 :(得分:2)

请查看(JSL)[https://www.npmjs.com/package/lib-jsl]。 它似乎符合给出的描述。

以下是一个示例:

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));

回复是:

[
  {
    "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"
  }
]

主要工作由规则workInProgress中定义的查询完成:

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

此规则可以理解为:

为了满足使用workInProgress的查询,我们定义了一个变量{workInProgress : '$bug'},然后我们使用规则{bug : '$bug'}的下一部分继续匹配数据库中的所有错误。这部分匹配所有错误,因为对象的形状(它的键:'bug')匹配数据库中的错误记录。该规则进一步要求$ bug变量为$ bind(ed)对包含$或中的相关状态值(打开和关闭)的模式。只有那些在$ bug中的状态值满足规则主体的所有部分的bug记录才有资格获得结果。

最终使用转换规范转换结果:transform : '$wip'字面上要求查询的$ wip变量中返回的所有值的数组。

答案 6 :(得分:1)

在Jeremy Wadhams评论之后,我实现了一个解析器,希望它可以为您提供帮助:

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

这个想法是使用$$and这样的$lte字符在特殊键中设置所有逻辑运算符。

例如:

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

致谢。

翻译为:

 ( age  <= 3 OR  name  = Joe  OR  ( age  = 5  AND  age  NOT IN (1,2,3) )  )  

答案 7 :(得分:0)

逻辑可以通过“ set”:[“ a”,“ b” ...]上的“ logicOp”:“ Operator”来实现 对于cHau的示例:

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

例如,该集合还可以具有其他属性/操作

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

在一个星期天有两个勺子的一种或多种冰淇淋类型,1种浇头和生奶油

"sunday": {
            "icecream": { 
                          "operators": [ "num": 2,
                                        "multipleOfSingleItem": "true"],
                          "set": ["chocolate", "strawberry", "vanilla"]
                        },
            "topping": {
                          "operators": ["num": 1],
                          "set": ["fudge", "caramel"]
                       },
            "whipcream": "true"
          }

答案 8 :(得分:0)

我想出了这种格式,其主要目标是读取尽可能接近实际SQL的内容。

这是打字稿中的Type定义:

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> { }

还是BNF(是?我的CS老师不会为之骄傲)

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

具有以下解析规则:

1:AND是可选的(出于可读性考虑,我通常将其添加)。如果在条件之间遗漏逻辑LogicalOperator,它将自动与AND连接 2:内部数组被解析为嵌套组(EG被包装在()中) 3:此类型不会连续(不幸地)限制多个逻辑运算符。尽管我可能抛出了运行时错误,但我只使用了最后一个来解决了这个问题。

以下是一些示例:

1和2 (并推断)

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

1或2

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

(1或2)和(3或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或(2 AND(3或4))

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

如您所见,如果要删除,和属性名,然后将[]替换为(),则基本上是SQL格式的条件

答案 9 :(得分:0)

Formula parser +一些JS代码将数据放入公式中,是另一种解决方法described with example in this answer

答案 10 :(得分:0)

我只是想通过在JavaScript中为答案中提到的JSON结构定义解析逻辑来提供帮助:https://stackoverflow.com/a/53215240/6908656

这对于那些在为此编写解析逻辑时费力的人很有帮助。

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");
    }
  };

因此,此处的参数arr将是条件数组,该条件的定义如附件链接所述。

答案 11 :(得分:0)

首先想到的是

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
                  ]
               }
            }
         ]
      }
   ]
}