Giter Site home page Giter Site logo

json-logic-ruby's Introduction

json-logic-ruby Build Status

Build complex rules, serialize them as JSON, and execute them in ruby.

json-logic-ruby is a ruby parser for JsonLogic. Other libraries are available for parsing this logic for Python and JavaScript at that link!

DANGER WILL ROBINSON!

This Ruby version of jwadhams/json-logic does not have the same functionality as the original Javascript version. In particular, method is not implemented at all and add_operation can only define operations with exactly one parameter. There are probably other deficiencies as well.

This does not mean that JsonLogic is not useful... IMHO, it is. However, the class of problems it can solve is a subset of the class of problems that jwadhams/json-logic can solve.

Contributions of expertise, time and energy are welcome to correct these deficiencies and bring the Ruby version up to parity with the Javascript version. Inquire within if you are interested.

Installation

gem install json_logic

Why use JsonLogic?

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.

JsonLogic has no setters, no loops, no functions or gotos. One rule leads to one decision, with no side effects and deterministic computation time.

Virtues

  1. Terse.
  2. Consistent. {"operator" : ["values" ... ]} Always.
  3. Secure. We never eval(). Rules only have read access to data you provide, and no write access to anything.
  4. Flexible. Easy to add new operators, easy to build complex structures.

Examples

simple

JSONLogic.apply({ "==" => [1, 1] }, {})
# => true

This is a simple rule, equivalent to 1 == 1. A few things about the format:

  1. The operator is always in the 「key」 position. There is only one key per JsonLogic rule.
  2. The values are typically an array.
  3. Each value can be a string, number, boolean, array (non-associative), or null

Compound

Here we're beginning to nest rules.

JSONLogic.apply(
{ "and" => [
  { ">" => [3,1] },
  { "<" => [1,3] }]
}, {})

# => true

In an infix language (like JavaScript) this could be written as:

( (3 > 1) && (1 < 3) )

Data-Driven

Obviously these rules aren't very interesting if they can only take static literal data. Typically jsonLogic will be called with a rule object and a data object. You can use the var operator to get attributes of the data object:

 JSONLogic.apply(
  { "var" => ["a"] }, # Rule
  { "a" => 1, "b" => 2 }  # Data
 )
 # => 1

If you like, we support syntactic sugar on unary operators to skip the array around values:

JSONLogic.apply(
  { "var" => "a" },
  { "a" => 1, "b" => 2 }
 )
# => 1

You can also use the var operator to access an array by numeric index:

JSONLogic.apply(
  { "var" => 1 },
  ["apple", "banana", "carrot"]
)
# => "banana"

Here's a complex rule that mixes literals and data. The pie isn't ready to eat unless it's cooler than 110 degrees, and filled with apples.

rules = JSON.parse(%Q|{ "and" : [
  {"<" : [ { "var" : "temp" }, 110 ]},
  {"==" : [ { "var" : "pie.filling" }, "apple" ] }
] }|)

data = JSON.parse(%Q|{ "temp" : 100, "pie" : { "filling" : "apple" } }|)

JSONLogic.apply(rules, data)

# => true

json-logic-ruby's People

Contributors

aschuster3 avatar barthez avatar bhgames avatar chrisliu-pw avatar elliottayoung avatar eropple avatar iamjwc avatar jcotton1123 avatar lewisf avatar mgleon08 avatar mjuszczak avatar ronin avatar sw030453 avatar tooooooooomy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

json-logic-ruby's Issues

Thank you!

Not an actual issue, just wanted to say thank you so much for creating this gem!

Logic doesn't work like the JS Library

JSONLogic.apply({ "+" => [{"var": "age"}, 1] }, {"age": 75})

returns NoMethodError: undefined method `var' for JSONLogic::Operation:Class

On JSONLogic.com, is 76.

Add size or length operator?

Hi, would you accept a PR to add a size or length operator? I would implement it as described here: jwadhams/json-logic#4. Though that issue provides a workaround it's a lot of code to do a very simple thing. Feel free to close this after replying.

"IN" Operator Fails When Accessed via "VAR" Operator

Problem Statement:

While trying to access a subset array from within a superset array using the "All" operator, I found that the "IN" operator does not function correctly when an array is accessed via the "VAR" operator.

Observed Behavior:
In the rule example below, the "IN" operator fails to correctly validate if all elements of the subset array are contained within the superset array:

Rule:

{ "all": [ {"var": "subset"}, {"in": [{"var": ""}, {"var": "superset"}]} ] }
Data

{ "subset": ["apple", "banana", "orange"], "superset": ["apple", "banana", "orange", "grape", "pear"] }

Expected Outcome:
The evaluation of this rule should yield True, as the subset array is fully included in the superset array.

Actual Outcome:
The rule incorrectly returns False.

Hint / Workaround:
When I specify the array directly within the rule instead of referencing it with the "VAR" operator, the "IN" operator behaves as expected:

Working Rule:
{ "all": [ {"var": "subset"}, {"in": [{"var": ""}, ["apple", "banana", "orange", "grape", "pear"]]} ] }

I suspect there might be a bug in the way the "IN" operator processes references to arrays. Your assistance in identifying and resolving the root cause of this behavior would be greatly appreciated.

`in` operator doesn't work correctly with `var`

I believe this is a bug, but please tell me if it's just my expectation that needs to be corrected:

irb(main):028:0> data = {"code": "45736123"}
=> {:code=>"45736123"}
irb(main):029:0> rule = {"in": ["123",{"var": "code"}]}
=> {:in=>["123", {:var=>"code"}]}
irb(main):030:0> JSONLogic.apply(rule,data)
=> false

Works fine when hardcoded:

irb(main):031:0> rule = {"in": ["123","45736123"]}
=> {:in=>["123", "45736123"]}
irb(main):032:0> JSONLogic.apply(rule,data)
=> true
irb(main):033:0> JSONLogic.apply(rule,nil)
=> true

Javascript only

This so-called 'gem' only works as advertised with Javascript.

Don't waste your time...

-- cc

Functional differences between this and original JS port

I've noticed that the operations in this port work entirely different then they do in the original JS port.
Specifically here:

return LAMBDAS[operator.to_s].call(interpolated, data) if is_standard?(operator)

JSONLogic is defined to accept the elements in an array as the argument to an operation.
However, your operations accept an array and the original data object.

With all the testing I've done so far, it seems like everything officially supported in the JS port works the same in this Ruby port.
Ex)

rule = {+: [1, 2]}
data = {}
JSONLogic.apply(rule, data) # 3

The result of this will be 3 in both ports ✅

However, if you were to define something like this in ruby:

rule = {+: [{var: 'data'}]}
data = {data: [1,2]}
JSONLogic.apply(rule, data) # 3

and in JS

rule = {"+": [{"var": "data"}]}
data = {"data": [1,2]}
jsonLogic.apply(rule, data) # 1

You end up with 3 in Ruby and 1 in JS. ❌

Ultimately this leads to inconsistencies when trying to develop cross-platform.

Are you looking for maintainers?

I noticed that the repo is not being actively maintained for quite a long time. I would be pleased to dedicate same time to help in the maintenance of this gem if you agreed. Please let me know what do you think @bhgames.

Date Comparison within Range in === Operator

I have a problem when trying to compare dates within a range using the === operator. The current implementation does not support this.

range = Date.parse('2024-01-01')..Date.parse('2024-12-31')
date = Date.parse('2024-07-22')

rule = {
  "===" => [
    { "var" => "range" },
    { "var" => "date" }
  ]
}

data = {
  "date" => date,
  "range" => range
}

result = JSONLogic.apply(rule, data)
# expected result is true, but in all cases it returns false

We could add the command "==="

'===' => ->(v, d) { v[0] == v[1] },

 '==='   => ->(v, d) { v[0] === v[1] }, 

IN operator fails when nil is passed as the array to search in

I've noticed that the in operator is failing when receiving nil values as the array.

Context:

We have been using JSONLogic for a couple of years now to evaluate rules on serialized objects at runtime. Most of the time, we don't know the actual values of the variables used in the rules since they are built by serializing objects retrieved from our database.

Current behavior:

When the variable (array to search in) doesn't exist in the object, a nil value is passed as the array, so in that case the gem is failing due to a NoMethodError: undefined method include?' for nil:NilClass`.

Here you can see an example:

rule = {"in" => ["value_to_search", {"var" => "array"}]}
variable = {"array" => nil} (Consider that nil is the value after some processing at runtime)

JSONLogic.apply(rule, variable)

The output:

Screen Shot 2022-06-28 at 19 11 21

Symbol handling seems spotty

Seems like symbolized keys in both data and filters give JSONLogic some grief.

[1] pry(main)> require 'json_logic'
=> true
[2] pry(main)> require 'active_support/core_ext/hash'
=> true

[3] pry(main)> filter = { "in": [ { "var": "foo.bar" }, [5] ] }
=> {:in=>[{:var=>"foo.bar"}, [5]]}
[4] pry(main)> good = { foo: { bar: 5 } }
=> {:foo=>{:bar=>5}}
[5] pry(main)> bad = { foo: { bar: 10 } }
=> {:foo=>{:bar=>10}}

[6] pry(main)> JSONLogic.apply(filter, good)
NoMethodError: undefined method `var' for JSONLogic::Operation:Class
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/json_logic-0.3/lib/json_logic/operation.rb:117:in `perform'

[9] pry(main)> JSONLogic.apply(filter.deep_stringify_keys, good.deep_stringify_keys)
=> true

Given the common use of symbols and the way a number of database libraries return JSON/JSONB data, it may be worth making it symbol/string agnostic.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.