Представление логики как данных в JSON

По соображениям бизнеса нам необходимо вытеснить некоторую условную логику во внешние файлы: желательно JSON.

Простой сценарий по сценарию можно обработать, добавив node следующим образом:

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

Несколько условий могут обрабатываться дополнительными значениями в массиве.

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

Однако, это немного запутывает, когда мы обрабатываем сложные условия, связанные с AND или OR.

Вопрос: существует ли стандартизованный (или даже широко распространенный) формат для представления такой логики в JSON? Как бы вы это сделали, если бы это было за вас?

ПРИМЕЧАНИЕ. Первый ответ был сделан редактируемой вики, поэтому его можно улучшить любым, кто считает, что это может быть.

Ответ 1

Если вы должны реализовать это с помощью стандартного 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" }]. Но есть случаи (например, пример), где прежний синтаксис не может точно представлять условие как написанное; вам потребуется дополнительная обманка, например, перевод условия или использование имен фиктивных свойств. Последний синтаксис всегда должен работать.

Ответ 2

Мне нужен был формат, который:

  • Поддержка сравнений, отличных от равенства.
  • Пусть переменные появляются в любой позиции, а не просто сравниваются с литералами.
  • Быть последовательным, кратким, безопасным и расширяемым.

Итак, я создал формат, который я называю JsonLogic. Правило - это объект JSON с оператором в позиции ключа и один или массив аргументов в позиции значения. (Вдохновленный Функции Amazon CloudFormation.) Любой аргумент может быть другим правилом, поэтому вы можете построить произвольно глубокую логику.

Я также написал для него два парсера: JsonLogic для JavaScript и JsonLogic для PHP.

Пример cHao будет записан как

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

var Вот оператор, чтобы получить свойство объекта "data", переданное вместе с объектом "rule" в парсер, например:

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

В GitHub (с модульными тестами и документацией) есть еще много возможных операторов (больше, не равно, в массиве, тройной и т.д.), и оба анализатора доступны.

Ответ 3

Кстати, IBM DB2 поддерживает логические операторы, закодированные в JSON.

Логические операции выглядят как нечто среднее между решением cHao и Amazon CloudFormation:

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

Операции сравнения выглядят для меня как транслитерированный SQL. (Вместо Amazon, Russellg или cHao движение к абстрактному синтаксическому дереву.)

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

Ответ 4

У меня была аналогичная потребность (для создания предложения sql where в javascript). Я создаю следующую функцию 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;
    }

Я создаю свой запрос. Так:

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

Когда я передаю свой запросOperation в ParseQuery, он возвращает ((tradedate = Thu Jul 24 17:30:37 EDT 2014)) AND (((systemid = 9)) ИЛИ ((systemid = 10)))

Мне нужно добавить некоторые преобразования типов и другие операторы, но основная структура работает.

Ответ 5

Мой коллега предложил это возможное решение:

"все условия OR были бы массивом, в то время как условия AND были бы объектами,

Например, OR может соответствовать любому из объектов массива:

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

И будет

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

Ответ 6

Пожалуйста, проверьте (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 как $bind (ed) против шаблонов, содержащих соответствующие значения состояния (открытые и закрытые) в пределах $или. Только те записи ошибок, значение статуса которых в $bug удовлетворяет всем частям тела правила, имеют право на результат.

Результат окончательно преобразуется с использованием спецификации преобразования: transform : '$wip', которая буквально запрашивает массив всех значений, возвращаемых в переменной $wip запроса.

Ответ 7

Я придумал этот формат с основной целью чтения как можно ближе к SQL.

Вот Тип определения в машинописи:

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. Внутренние массивы анализируются как вложенные группы (например, они заключены в ())
  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 ИЛИ 3)

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

1 И 2 ИЛИ 3

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

1 ИЛИ (2 И (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.

Ответ 8

Логика может быть реализована с помощью "logicOp": "Operator" для "set": ["a", "b"...] Для примера 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"
          }

Ответ 10

Вы пытались использовать такой сайт, который помог бы вам построить свою логику? http://jsonlogic.com