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

Construct JsonataQuery through C# models #28

Open
huangshu91 opened this issue Jul 23, 2024 · 9 comments
Open

Construct JsonataQuery through C# models #28

huangshu91 opened this issue Jul 23, 2024 · 9 comments

Comments

@huangshu91
Copy link

Hi,

I was wondering if there is any discussion around the ability to build a JSONata query through C# models directly instead of the query language. We have some use cases where customers will provide their own query but due to unfamiliarity with the JSONata language may want to construct the query directly through C# types.

This ask is related to the issue I previously created (#26) which I think is resolved through reflection. For example, being able to provide JSonataQuery with either a JSONata query or some model that represents the AST.

@mikhail-barg
Copy link
Owner

Hi @huangshu91, thanks for the idea! This would probably need something like a DOM approach. And I actually like it more than simply exposing current AST objects.

Still it would require some research, as this is not as straightforward as regular JSON DOM, because with JSON you always have a valid DOM at each step of construction. While for expressions like JSONata queries this might not be a case.

I think I'll have to look for some existing expression construction approaches. The one that comes to mind first would be LINQ Expression Trees, but there should be more to choose from, depending on expected use-cases.

Will you be able to describe your use-cases in possible details, so I could get a better idea on what the expectations are?

@mikhail-barg
Copy link
Owner

Maybe some examples of queries, you are planning to construct?

@huangshu91
Copy link
Author

huangshu91 commented Jul 29, 2024

here's a modified example:

$ ~> | $ | { "foo": ( $customParties := $filter( root.customParties, function($bar) { $type($bar.displayName) != "null" } ).{ "$class": "___", "displayName": displayName }; $customParties := $map($customParties, function($bar) { $merge([foo[0], $bar]) }); $foo := $append( [ { "$class": "___", "displayName": displayName } ], $customParties ); $foo := $append( $foo, { "$class": "___", "displayName": displayName } ); $x := $count($foo) > 0; $x ? $foo : $undefined ) } |

We have some usage of functions, logic, etc. There are certain things we want to validate and allow/disallow (the AST issue we discussed previously) but also constructing this transform is quite painful if the client is unfamiliar with jsonata syntax. For example, we've already disallowed regexes though our check is very primitive. It's still early stages so we arn't sure what other types of queries we will want to support but this is just one use-case that we already have.

@mikhail-barg
Copy link
Owner

Thanks for the example!

It looks rather complicated though, and I'm still not sure what kind of DOM model would be OK for such complex query composition.

E.g. if implemented straightforward, the sub-part $x := $count($foo) > 0; would probably look like this:

Node node = new AssignmentNode(
	new VariableNode("x"),
	new ComparisonOperatorNode(
		ComparisonOperator.Greater,
		new FunctionCallNode(
			"count",
			new VariableNode("foo")
		),
		new NumberIntNode(0)
	)
);

Not sure if it's ok. What do you think?

@mikhail-barg
Copy link
Owner

Just for a consistency sake, actual (and surprisingly working) code would be like

Node node = new AssignmentNode(
    "x",
    new ComparisonOperatorNode(
        ComparisonOperator.ComparisonGreater,
        new FunctionCallNode(
            new VariableNode("count"),
            new List<Node>() { new VariableNode("foo") }
        ),
        new NumberIntNode(0)
    )
);

@mikhail-barg
Copy link
Owner

And for this query

(
  $factorial := function($x) {
    $x <= 1 ? 1 : $x * $factorial($x-1)
  };
  $factorial(5)
)

corresponding expression would be like:

Node node = new BlockNode(
    new List<Node> {
        new AssignmentNode(
            "factorial",
            new LambdaNode(
                false,
                new List<string>(){ "x" },
                null,
                new ConditionalNode(
                    new ComparisonOperatorNode(
                        ComparisonOperator.ComparisonLessEqual,
                        new VariableNode("x"),
                        new NumberIntNode(1)
                    ),
                    new NumberIntNode(1),
                    new NumericOperatorNode(
                        NumericOperatorNode.NumericOperator.NumericMultiply,
                        new VariableNode("x"),
                        new FunctionCallNode(
                            new VariableNode("factorial"),
                            new List<Node>() {
                                new NumericOperatorNode(
                                    NumericOperatorNode.NumericOperator.NumericSubtract,
                                    new VariableNode("x"),
                                    new NumberIntNode(1)
                                )
                            }
                        )
                    )
                )
            )
        ),
        new FunctionCallNode(
            new VariableNode("factorial"),
            new List<Node>() {
                new NumberIntNode(5)
            }
        )
    }
);

Looks like an overcomplication, isn't it?

@mikhail-barg
Copy link
Owner

On the other hand, it would not hurt too much to just expose this possibility as a public api, right? Maybe with a warning that it's not a stable api and is a subject to change.

@huangshu91 will this do for you?

@mikhail-barg
Copy link
Owner

@huangshu91 I've released a v2.7.0 package with new DOM API, as discussed above.

Please have a look at samples: https://github.com/mikhail-barg/jsonata.net.native/blob/master/src/Jsonata.Net.Native.Tests/DomTests.cs (those are generally based on the examplese i've shown above).

A bit more details are here: https://github.com/mikhail-barg/jsonata.net.native?tab=readme-ov-file#query-dom-introspection-and-query-creation-via-dom

Please tell me, if this API suits your needs.

Please also note that I currently consider new API to be experimental, so there could be some issues (I am still not sure on non-public Node classes: PredicateNode_, DotNode_, SingletonArrayNode_) or future breaking changes.

@huangshu91
Copy link
Author

Sorry for the late response, was out the last few weeks. This is fantastic and suits our needs. We will integrate these changes and give feedback. Thank you!

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

2 participants