TotalTechGeek / json-logic-engine

Construct complex rules with JSON & process them.
MIT License
43 stars 9 forks source link

filter does not work with the second parameter as var #28

Closed tatarysh closed 3 months ago

tatarysh commented 3 months ago

I noticed that in filter, I can't use var (probably loses context to data), it would be nice if you could also use data from data. here is my example of use:

Data

{
    "locale": "pl",
    "locales": [
        {
            "name": "Israel",
            "code": "he",
            "flag": "🇮🇱",
            "iso": "he-IL",
            "dir": "rtl"
        },
        {
            "name": "українська",
            "code": "ue",
            "flag": "🇺🇦",
            "iso": "uk-UA",
            "dir": "ltr"
        },
        {
            "name": "Polski",
            "code": "pl",
            "flag": "🇵🇱",
            "iso": "pl-PL",
            "dir": "ltr"
        }
    ]
}

Valid JSON

{
    "filter": [
        {
            "var": "locales"
        },
        {
            "!==": [
                {
                    "var": "code"
                },
                "pl"
            ]
        }
    ]
}

Invalid JSON

{
    "filter": [
        {
            "var": "locales"
        },
        {
            "!==": [
                {
                    "var": "code"
                },
                {
                    "var": "locale"
                }
            ]
        }
    ]
}

in both cases I should receive:

[{"name":"Israel","code":"he","flag":"🇮🇱","iso":"he-IL","dir":"rtl"},{"name":"українська","code":"ue","flag":"🇺🇦","iso":"uk-UA","dir":"ltr"}]

maybe some additional operator? or maybe _.locale to get access the original data?

TotalTechGeek commented 3 months ago

This is actually one of the reasons I forked json-logic:)

This is the "handlebars" style Traversal capability.

const f = engine.build({
    filter: [
        { var:  'locales' },
        { '===': [{ var: 'code' }, { var: '../../locale' }] }
    ]
})

f(data) // [ { name: 'Polski', code: 'pl', flag: '🇵🇱', iso: 'pl-PL', dir: 'ltr' } ]
tatarysh commented 3 months ago

Works beautifully! Thank you! it would be useful to add a similar example in the documentation.

There is a small error in the Filter operator in the "Higher Order Operators" table. The instruction for this operator is filter not Filter.

tatarysh commented 3 months ago

I have anothier question @TotalTechGeek, I don't know if this is the expected result, but it looks like it is not:

{
    "get": [
        {
            "xs": "100",
            "sm": "99",
            "md": "60",
            "lg": "30",
            "xl": "12"
        },
        {
            "var": "context.root.$vuetify.breakpoint.name"
        }
    ]
}
{
    "context": {
        "root": {
            "$vuetify": {
                "breakpoint": {
                    "name": "xs"
                }
            }
        }
    }
}

result is null, but it should be: "100"


but it's already working properly:

{
    "get": [
        {
            "var": "values"
        },
        {
            "var": "context.root.$vuetify.breakpoint.name"
        }
    ]
}
{
    "values": {
        "xs": "100",
        "sm": "99",
        "md": "60",
        "lg": "30",
        "xl": "12"
    },
    "context": {
        "root": {
            "$vuetify": {
                "breakpoint": {
                    "name": "xs"
                }
            }
        }
    }
}
tatarysh commented 3 months ago

I think this is because when it is an object, the object key is taken and the method is looked for to execute, but if the method is not found, it gets null instead of the value that was set. maybe it will be a good idea to check whether it is an object, whether it has only one key and whether the key in the object is a registered method, if so, run it, if not, give the value of the object

TotalTechGeek commented 3 months ago

Hi @tatarysh

What settings are you running the engine with? Have you added any methods (like xs)?

There are two main ways to solve this:

If you want to embed data in your logic without risking it running as a method, the recommended approach is to use preserve.

{
    "get": [{
            "preserve": {
                "xs": "100",
                "sm": "99",
                "md": "60",
                "lg": "30",
                "xl": "12"
            }
        },
        {
            "var": "context.root.$vuetify.breakpoint.name"
        }
    ]
}

preserve will guarantee that anything passed into it will not be invoked as a method. This also helps prevent ambiguity for situations where you might have xs or something registered as a method in the engine.

If you haven't added other methods to the engine (thus no ambiguity), it's possible to enable permissive mode on the Logic Engine, where an object that fails to be interpeted will be treated as data.

TotalTechGeek commented 3 months ago

P.S. here is a link with an example of permissive mode: https://github.com/TotalTechGeek/json-logic-engine/issues/6#issuecomment-1494786753

Currently, permissive mode doesn't check if the object only has one key; it just checks if the first key it sees is a registered method.

I could see an argument for allowing logic to have a single key, though I still feel that preserve is the safest and least ambiguous way to embed data in your logic.

I may spend some time thinking about whether I want to allow some further customizability.

TotalTechGeek commented 3 months ago

@tatarysh While I still stand by my preferences communicated above (preserve seems like a simple way to do this, and should be a good ecosystem standard), I've decided to implement a way for users to customize the behavior to their preferences.

class DataEngine extends LogicEngine {
  isData (logic, firstKey) {
    if (Object.keys(logic).length > 1) return true
    return !(firstKey in this.methods)
  }
}

As of the newly published v1.3.2, you can extend the LogicEngine to specify the isData behavior.

The implementation for isData there will:

Matching the behavior you desire :)

tatarysh commented 3 months ago

I'll test it tomorrow at work, thank you for your support :)