Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to use JSONLogic as a query language? #15

Open
FluorescentHallucinogen opened this issue Nov 3, 2016 · 8 comments
Open

Comments

@FluorescentHallucinogen
Copy link

Is it possible to use JSONLogic as a query language to filter JSON? Something like this:

var data =
[
	{
		"id": 1,
		"name": "Oliver",
		"email": "[email protected]"
	},
	{
		"id": 2,
		"name": "Jack",
		"email": "[email protected]"
	},
	{
		"id": 3,
		"name": "Harry",
		"email": "[email protected]"
	},
	{
		"id": 4,
		"name": "Jacob",
		"email": "[email protected]"
	},
	{
		"id": 5,
		"name": "Charlie",
		"email": "[email protected]"
	},
	{
		"id": 6,
		"name": "Thomas",
		"email": "[email protected]"
	},
	{
		"id": 7,
		"name": "George",
		"email": "[email protected]"
	},
	{
		"id": 8,
		"name": "Oscar",
		"email": "[email protected]"
	},
	{
		"id": 9,
		"name": "James",
		"email": "[email protected]"
	},
	{
		"id": 10,
		"name": "William",
		"email": "[email protected]"
	}
]

var filter =
{"or": [
	{"and": [
		{">=": [
			{"var": "id"}, 3
		]},
		{"<=": [
			{"var": "id"}, 5
		]}
	]},
	{">": [
		{"var": "id"}, 7
	]}
]};

jsonLogic.filter(filter, data);

It should return:

[
	{
		"id": 3,
		"name": "Harry",
		"email": "[email protected]"
	},
	{
		"id": 4,
		"name": "Jacob",
		"email": "[email protected]"
	},
	{
		"id": 5,
		"name": "Charlie",
		"email": "[email protected]"
	},
	{
		"id": 8,
		"name": "Oscar",
		"email": "[email protected]"
	},
	{
		"id": 9,
		"name": "James",
		"email": "[email protected]"
	},
	{
		"id": 10,
		"name": "William",
		"email": "[email protected]"
	}
]
@jwadhams
Copy link
Owner

jwadhams commented Nov 3, 2016

You could do this by combining JavaScript's filter with that rule:

data.filter(function(item){
  return jsonLogic.apply(filter, item);
})

You could also monkey-patch this onto the library quickly like:

jsonLogic.filter = function(rule, data){
    if(typeof data.filter !== "function"){
        throw "jsonLogic.filter requires data be an Array";
    }
    return data.filter(function(item){
        return jsonLogic.apply(rule, item);
    });
};

By the way, there's a special three-argument syntax for < and <= that can simplify your rule:

{ "or": [
    { "<=": [ 3, { "var": "id" }, 5 ] },
    { ">": [ { "var": "id" }, 7 ] }
] };

I hesitate to bake this into the spec, just because I don't have time right now to write appropriate unit tests for it.

Does this get you where you need to go?

@FluorescentHallucinogen
Copy link
Author

Works like a charm!

You can test and compare different JSON query languages online at http://jsonquerytool.com.

Looks like JSON Logic can be used as a good alternative to them, because 1) the rule of query can be so complex as I want and 2) the query is a JSON itself.

I want to create a server that uses only JSON for everything: JSON database, JSON API (JSON Logic for query language and JSON Patch for sync changes between client and server), JSON Schema (http://json-schema.org) for validating submitted data and describing API.

Not sure that there is a ready solution that uses all these things together.

Have you heard anything about JSON Patch (http://jsonpatch.com)?

I'm very inspired by JSON Patch idea, but seems the syntax might be easier and cleaner in prefix notation and without "op". Something like this:

[
    {"replace": [{"path": "/baz"}, {"value": "boo"}]},
    {"add": [{"path": "/hello"}, {"value": "world"}]},
    {"remove": {"path": "/foo"}}
]

or even:

[
    {"replace": ["/baz", "boo"]},
    {"add": ["/hello", "world"]},
    {"remove": "/foo"}
]

instead of patch:

[
    {"op": "replace", "path": "/baz", "value": "boo"},
    {"op": "add", "path": "/hello", "value": "world"},
    {"op": "remove", "path": "/foo"}
]

The original JSON document:

{
    "baz": "qux",
    "foo": "bar"
}

The result:

{
    "baz": "boo",
    "hello": "world"
}

And looks like JSON Logic can be used instead of JSON Pointer to specify the parts of the document to operate on.

BTW, seems the syntax of JSON Logic might be also easier and cleaner without "var". Something like this:

var rules =
{
    "and": [
        {"<": ["temp", 110]},
        {"==": ["pie.filling", "apple"]}
    ]
};

instead of:

var rules =
{
    "and": [
        {"<": [{"var": "temp"}, 110]},
        {"==": [{"var": "pie.filling"}, "apple"]}
    ]
};
var data =
{
    "temp": 100,
    "pie": {
        "filling": "apple"
    }
};
jsonLogic.apply(rules, data);
// true

What do you think about all this?

@jwadhams
Copy link
Owner

I think you're right, you could pretty quickly patch in replace and add and remove operations with add_operation that act directly on the data object.

I'm not familiar with JsonPointer, but I have used dotty--I just suggested a way to mooch functionality off that library into JsonLogic rules in this issue.

Re: cleaner syntax without var, in your example how does the parser understand that "temp" and "pie.filling" are data names, but "apple" is a literal string? It's totally doable in a command like {"remove":"/foo"} where that argument can't be anything but a variable reference, but I don't think it'll work everywhere.

@FluorescentHallucinogen
Copy link
Author

FluorescentHallucinogen commented Nov 11, 2016

in your example how does the parser understand that "temp" and "pie.filling" are data names, but "apple" is a literal string?

There are 2 possible ways:

The first (not recommended because limits the flexibility):

  • Comparisons should always be between a variable and a string, and never between variables (and never between strings?).
  • The variable should always be on the left hand side of the comparison, and the string on the right.

The second:

  • Use $temp for variables and temp for strings.

Why I raised the issue of easier and cleaner syntax? What do you think about the syntax of https://npmjs.com/package/json-logic? It also uses "var" ("variables"), but it uses "operation". Why you don't use "operation" ("op")?

@FluorescentHallucinogen
Copy link
Author

What about "filter" operation instead of jsonLogic.filter function to filter JSON?

What about sorting (ordering) JSON?

@FluorescentHallucinogen
Copy link
Author

  1. How to retrieve the whole object (the key and the value):

{"name": "Oliver"}

from data:

{
    "id": 1,
    "name": "Oliver",
    "email": "[email protected]"
}

?

  1. How to retrieve the only needed objects:
[
    {
        "name": "Oliver",
        "email": "[email protected]"
    },
    {
        "name": "Jack",
        "email": "[email protected]"
    }
]

from data:

[
    {
        "id": 1,
        "name": "Oliver",
        "email": "[email protected]"
    },
    {
        "id": 2,
        "name": "Jack",
        "email": "[email protected]"
    }
]

?

@FluorescentHallucinogen
Copy link
Author

@jwadhams, any ideas?

@josephdpurcell
Copy link

The title of the issue caught my attention.

I really like JSON Logic. It seems incredibly powerful.

I think the issue described here: #116 and the related PR could make the objective of this ticket far easier.

Assuming the traverse flag gets added, one could do the following:

function json_filter(logic, data, jsonlogic) {
  return data.filter(function(item){
    // NOTE: the proposed implementation of traverse gives us back an array, so we de-array-ify it.
    return jsonlogic.apply(logic, item)[0];
  });
};

jsonLogic.add_operation('json_filter', (logic, data, jsonlogic) => {
  return json_filter(logic, data, jsonlogic);
}, {
  traverse: false
});

Then, I did the following:

Expand to view example jsonLogic.apply
jsonLogic.apply(
{"json_filter":{"or":[{"<=":[3,{"var":"id"},5]},{">":[{"var":"id"},7]}]}},
[
      {
          "id": 1,
          "name": "Oliver",
          "email": "[email protected]"
      },
      {
          "id": 2,
          "name": "Jack",
          "email": "[email protected]"
      },
      {
          "id": 3,
          "name": "Harry",
          "email": "[email protected]"
      },
      {
          "id": 4,
          "name": "Jacob",
          "email": "[email protected]"
      },
      {
          "id": 5,
          "name": "Charlie",
          "email": "[email protected]"
      },
      {
          "id": 6,
          "name": "Thomas",
          "email": "[email protected]"
      },
      {
          "id": 7,
          "name": "George",
          "email": "[email protected]"
      },
      {
          "id": 8,
          "name": "Oscar",
          "email": "[email protected]"
      },
      {
          "id": 9,
          "name": "James",
          "email": "[email protected]"
      },
      {
          "id": 10,
          "name": "William",
          "email": "[email protected]"
      }
  ]
);

Results:

[
    {
        "id": 3,
        "name": "Harry",
        "email": "[email protected]"
    },
    {
        "id": 4,
        "name": "Jacob",
        "email": "[email protected]"
    },
    {
        "id": 5,
        "name": "Charlie",
        "email": "[email protected]"
    },
    {
        "id": 8,
        "name": "Oscar",
        "email": "[email protected]"
    },
    {
        "id": 9,
        "name": "James",
        "email": "[email protected]"
    },
    {
        "id": 10,
        "name": "William",
        "email": "[email protected]"
    }
]

Pretty cool!

And, one more thing for kicks that is only tangentially related. I had the same thought regarding JSON Patch! It would be neat to be able to leverage JSON Patch within JSON Logic.

Well, you could do such a thing! I'm attaching an example.

Expand to see JSON Patch example
import * as jsonPatch from 'fast-json-patch';

function applyPatch(logic, data, jsonlogic) {
  return jsonPatch.applyOperation(data, logic[0]).newDocument;
}

jsonLogic.add_operation('jsonPatch.applyPatch', applyPatch, {
  traverse: false
});

jsonLogic.apply(
{"jsonPatch.applyPatch":[{"op":"replace","path":"/0/id","value":"99"}]},
// the data from earlier
);

Results:

[
    {
        "id": "99",
        "name": "Oliver",
        "email": "[email protected]"
    },

All that to say, if the traverse flag gets merged (or something similar), or if you patch your own version of JSON Logic, the sky is the limit because it won't traverse into your operation's logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants