From 9c79c8ccafcccd571bdf0a253c81bde30b042edf Mon Sep 17 00:00:00 2001 From: Courela Date: Mon, 3 Apr 2023 00:31:44 +0100 Subject: [PATCH 01/10] Multiple transformations in array --- JUST.net/JsonTransformer.cs | 28 ++++++++++++++++++++++++++++ UnitTests/ReadmeTests.cs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/JUST.net/JsonTransformer.cs b/JUST.net/JsonTransformer.cs index 4dc8b64..ad7d537 100644 --- a/JUST.net/JsonTransformer.cs +++ b/JUST.net/JsonTransformer.cs @@ -230,7 +230,35 @@ private void ParsePropertyFunction(IDictionary parentArray, IDic case "eval": EvalOperation(property, arguments, parentArray, currentArrayToken, ref loopProperties, ref tokensToAdd); break; + case "transform": + TranformOperation(property, arguments, parentArray, currentArrayToken, ref loopProperties); + break; + } + } + + private void TranformOperation(JProperty property, string arguments, IDictionary parentArray, IDictionary currentArrayToken, ref List loopProperties) + { + if (property.Value.Type == JTokenType.Array) + { + JToken originalInput = Context.Input; + for (int i = 0; i < property.Value.Count(); i++) + { + JToken token = property.Value[i]; + if (token.Type == JTokenType.String) + { + var obj = ParseFunction(token.Value(), parentArray, currentArrayToken); + token.Replace(GetToken(obj)); + } + else + { + RecursiveEvaluate(ref token, parentArray, currentArrayToken); + } + Context.Input = token; + } + + Context.Input = originalInput; } + property.Parent.Replace(property.Value[property.Value.Count() - 1]); } private void PostOperationsBuildUp(ref JToken parentToken, List tokenToForm) diff --git a/UnitTests/ReadmeTests.cs b/UnitTests/ReadmeTests.cs index ec044c3..610a8c5 100644 --- a/UnitTests/ReadmeTests.cs +++ b/UnitTests/ReadmeTests.cs @@ -254,5 +254,37 @@ public void TypeCheck() Assert.AreEqual("{\"isNumberTrue1\":true,\"isNumberTrue2\":true,\"isNumberFalse\":false,\"isBooleanTrue\":true,\"isBooleanFalse\":false,\"isStringTrue\":true,\"isStringFalse\":false,\"isArrayTrue\":true,\"isArrayFalse\":false}", result); } + + [Test] + public void MultipleTransformsScalarResult() + { + const string input = "{\"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ]}"; + const string transformer = + "{ \"result\": " + + "{ \"#transform($)\": [ " + + "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.d[0]),#currentvalue()),True,yes,no)\" } } }, " + + "{ \"condition\": \"#valueof($.condition)\" }," + + "\"#exists($.condition[?(@.test=='yes')])\" ] } }"; + + var result = new JsonTransformer().Transform(transformer, input); + + Assert.AreEqual("{\"result\":true}", result); + } + + [Test] + public void MultipleTransformsObjectResult() + { + const string input = "{\"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ]}"; + const string transformer = + "{ \"result\": " + + "{ \"#transform($)\": [ " + + "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.d[0]),#currentvalue()),True,yes,no)\" } } }, " + + "{ \"condition\": \"#valueof($.condition)\" }," + + "{ \"a\": \"#exists($.condition[?(@.test=='yes')])\" } ] } }"; + + var result = new JsonTransformer().Transform(transformer, input); + + Assert.AreEqual("{\"result\":{\"a\":true}}", result); + } } } \ No newline at end of file From bf7d09f79a363cf3021b78c0eb830fb9c0aeda28 Mon Sep 17 00:00:00 2001 From: Courela Date: Tue, 4 Apr 2023 08:47:29 +0100 Subject: [PATCH 02/10] Multiple transforms inside loop --- JUST.net/JsonTransformer.cs | 34 +++++++++++++++++++++++++++++++--- UnitTests/ReadmeTests.cs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/JUST.net/JsonTransformer.cs b/JUST.net/JsonTransformer.cs index ad7d537..8144e7f 100644 --- a/JUST.net/JsonTransformer.cs +++ b/JUST.net/JsonTransformer.cs @@ -231,16 +231,44 @@ private void ParsePropertyFunction(IDictionary parentArray, IDic EvalOperation(property, arguments, parentArray, currentArrayToken, ref loopProperties, ref tokensToAdd); break; case "transform": - TranformOperation(property, arguments, parentArray, currentArrayToken, ref loopProperties); + TranformOperation(property, arguments, parentArray, currentArrayToken); break; } } - private void TranformOperation(JProperty property, string arguments, IDictionary parentArray, IDictionary currentArrayToken, ref List loopProperties) + private void TranformOperation(JProperty property, string arguments, IDictionary parentArray, IDictionary currentArrayToken) { + string[] argumentArr = ExpressionHelper.SplitArguments(arguments, Context.EscapeChar); + + object functionResult = ParseArgument(parentArray, currentArrayToken, argumentArr[0]); + if (!(functionResult is string jsonPath)) + { + throw new ArgumentException($"Invalid path for #transform: '{argumentArr[0]}' resolved to null!"); + } + + JToken selectedToken = null; + string alias = null; + if (argumentArr.Length > 1) + { + alias = ParseArgument(parentArray, currentArrayToken, argumentArr[1]) as string; + if (!(currentArrayToken?.ContainsKey(alias) ?? false)) + { + throw new ArgumentException($"Unknown loop alias: '{argumentArr[1]}'"); + } + JToken input = alias != null ? currentArrayToken?[alias] : currentArrayToken?.Last().Value ?? Context.Input; + var selectable = GetSelectableToken(currentArrayToken[alias], Context); + selectedToken = selectable.Select(argumentArr[0]); + } + else + { + var selectable = GetSelectableToken(currentArrayToken?.Last().Value ?? Context.Input, Context); + selectedToken = selectable.Select(argumentArr[0]); + } + if (property.Value.Type == JTokenType.Array) { JToken originalInput = Context.Input; + Context.Input = selectedToken; for (int i = 0; i < property.Value.Count(); i++) { JToken token = property.Value[i]; @@ -251,7 +279,7 @@ private void TranformOperation(JProperty property, string arguments, IDictionary } else { - RecursiveEvaluate(ref token, parentArray, currentArrayToken); + RecursiveEvaluate(ref token, i == 0 ? parentArray : null, i == 0 ? currentArrayToken : null); } Context.Input = token; } diff --git a/UnitTests/ReadmeTests.cs b/UnitTests/ReadmeTests.cs index 610a8c5..7ec8833 100644 --- a/UnitTests/ReadmeTests.cs +++ b/UnitTests/ReadmeTests.cs @@ -286,5 +286,39 @@ public void MultipleTransformsObjectResult() Assert.AreEqual("{\"result\":{\"a\":true}}", result); } + + [Test] + public void MultipleTransformsOverSelectedToken() + { + const string input = "{ \"select\": {\"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ]} }"; + const string transformer = + "{ \"result\": " + + "{ \"#transform($.select)\": [ " + + "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.d[0]),#currentvalue()),True,yes,no)\" } } }, " + + "{ \"condition\": \"#valueof($.condition)\" }," + + "{ \"a\": \"#exists($.condition[?(@.test=='yes')])\" } ] } }"; + + var result = new JsonTransformer().Transform(transformer, input); + + Assert.AreEqual("{\"result\":{\"a\":true}}", result); + } + + [Test] + public void MultipleTransformsWithinLoop() + { + const string input = "{ \"select\": [{ \"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ] }, { \"d\": [ \"four\", \"five\", \"six\" ], \"values\": [ \"z\", \"c\", \"n\" ] }] }"; + const string transformer = + "{ \"result\": {" + + " \"#loop($.select,selectLoop)\": { " + + "\"#transform($)\": [ " + + "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#currentvalueatpath($.d[0],selectLoop),#currentvalue()),True,yes,no)\" } } }, " + + "{ \"condition\": \"#valueof($.condition)\" }," + + "{ \"a\": \"#exists($.condition[?(@.test=='yes')])\" } ] " + + " } } }"; + + var result = new JsonTransformer().Transform(transformer, input); + + Assert.AreEqual("{\"result\":[{\"a\":true},{\"a\":false}]}", result); + } } } \ No newline at end of file From c5558db5e5722f35251b6cd9f276dcccfe7a9ca2 Mon Sep 17 00:00:00 2001 From: Courela Date: Fri, 7 Apr 2023 15:17:57 +0100 Subject: [PATCH 03/10] Create MultipleTransformations unit test class; Readme unit test for #transform --- UnitTests/MultipleTransformations.cs | 74 ++++++++++++++++++++++++++++ UnitTests/ReadmeTests.cs | 63 ++--------------------- 2 files changed, 78 insertions(+), 59 deletions(-) create mode 100644 UnitTests/MultipleTransformations.cs diff --git a/UnitTests/MultipleTransformations.cs b/UnitTests/MultipleTransformations.cs new file mode 100644 index 0000000..b0fd8e2 --- /dev/null +++ b/UnitTests/MultipleTransformations.cs @@ -0,0 +1,74 @@ +using NUnit.Framework; + +namespace JUST.UnitTests +{ + [TestFixture] + public class MultipleTransformations + { + [Test] + public void MultipleTransformsScalarResult() + { + const string input = "{\"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ]}"; + const string transformer = + "{ \"result\": " + + "{ \"#transform($)\": [ " + + "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.d[0]),#currentvalue()),True,yes,no)\" } } }, " + + "{ \"intermediate_transform\": \"#valueof($.condition)\" }," + + "\"#exists($.intermediate_transform[?(@.test=='yes')])\" ] } }"; + + var result = new JsonTransformer().Transform(transformer, input); + + Assert.AreEqual("{\"result\":true}", result); + } + + [Test] + public void MultipleTransformsObjectResult() + { + const string input = "{\"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ]}"; + const string transformer = + "{ \"object\": " + + "{ \"#transform($)\": [ " + + "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.d[0]),#currentvalue()),True,yes,no)\" } } }, " + + "{ \"intermediate_transform\": \"#valueof($.condition)\" }," + + "{ \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] } }"; + + var result = new JsonTransformer().Transform(transformer, input); + + Assert.AreEqual("{\"object\":{\"result\":true}}", result); + } + + [Test] + public void MultipleTransformsOverSelectedToken() + { + const string input = "{ \"select\": {\"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ]} }"; + const string transformer = + "{ \"select_token\": " + + "{ \"#transform($.select)\": [ " + + "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.d[0]),#currentvalue()),True,yes,no)\" } } }, " + + "{ \"intermediate_transform\": \"#valueof($.condition)\" }," + + "{ \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] } }"; + + var result = new JsonTransformer().Transform(transformer, input); + + Assert.AreEqual("{\"select_token\":{\"result\":true}}", result); + } + + [Test] + public void MultipleTransformsWithinLoop() + { + const string input = "{ \"select\": [{ \"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ] }, { \"d\": [ \"four\", \"five\", \"six\" ], \"values\": [ \"z\", \"c\", \"n\" ] }] }"; + const string transformer = + "{ \"loop\": {" + + " \"#loop($.select,selectLoop)\": { " + + "\"#transform($)\": [ " + + "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#currentvalueatpath($.d[0],selectLoop),#currentvalue()),True,yes,no)\" } } }, " + + "{ \"intermediate_transform\": \"#valueof($.condition)\" }," + + "{ \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] " + + " } } }"; + + var result = new JsonTransformer().Transform(transformer, input); + + Assert.AreEqual("{\"loop\":[{\"result\":true},{\"result\":false}]}", result); + } + } +} \ No newline at end of file diff --git a/UnitTests/ReadmeTests.cs b/UnitTests/ReadmeTests.cs index 7ec8833..0a27be8 100644 --- a/UnitTests/ReadmeTests.cs +++ b/UnitTests/ReadmeTests.cs @@ -256,69 +256,14 @@ public void TypeCheck() } [Test] - public void MultipleTransformsScalarResult() + public void Transform() { - const string input = "{\"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ]}"; - const string transformer = - "{ \"result\": " + - "{ \"#transform($)\": [ " + - "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.d[0]),#currentvalue()),True,yes,no)\" } } }, " + - "{ \"condition\": \"#valueof($.condition)\" }," + - "\"#exists($.condition[?(@.test=='yes')])\" ] } }"; + const string input = "{ \"spell\": [\"one\", \"two\", \"three\"], \"letters\": [\"z\", \"c\", \"n\"], \"nested\": { \"spell\": [\"one\", \"two\", \"three\"], \"letters\": [\"z\", \"c\", \"n\"] },\"array\": [{ \"spell\": [\"one\", \"two\", \"three\"], \"letters\": [\"z\", \"c\", \"n\"] }, { \"spell\": [\"four\", \"five\", \"six\"], \"letters\": [\"z\", \"c\", \"n\"] } ]}"; + const string transformer = "{ \"scalar\": { \"#transform($)\": [{ \"condition\": { \"#loop($.letters)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.spell[0]),#currentvalue()),True,yes,no)\" } } }, \"#exists($.condition[?(@.test=='yes')])\"] }, \"object\": { \"#transform($)\": [{ \"condition\": { \"#loop($.letters)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.spell[0]),#currentvalue()),True,yes,no)\" } } }, { \"intermediate_transform\": \"#valueof($.condition)\" }, { \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] }, \"select_token\": { \"#transform($.nested)\": [{ \"condition\": { \"#loop($.letters)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.spell[0]),#currentvalue()),True,yes,no)\" } } }, { \"intermediate_transform\": \"#valueof($.condition)\" }, { \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] }, \"loop\": { \"#loop($.array,selectLoop)\": { \"#transform($)\": [{ \"condition\": { \"#loop($.letters)\": { \"test\": \"#ifcondition(#stringcontains(#currentvalueatpath($.spell[0],selectLoop),#currentvalue()),True,yes,no)\" } } }, { \"intermediate_transform\": \"#valueof($.condition)\" }, { \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] } } } "; var result = new JsonTransformer().Transform(transformer, input); - Assert.AreEqual("{\"result\":true}", result); - } - - [Test] - public void MultipleTransformsObjectResult() - { - const string input = "{\"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ]}"; - const string transformer = - "{ \"result\": " + - "{ \"#transform($)\": [ " + - "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.d[0]),#currentvalue()),True,yes,no)\" } } }, " + - "{ \"condition\": \"#valueof($.condition)\" }," + - "{ \"a\": \"#exists($.condition[?(@.test=='yes')])\" } ] } }"; - - var result = new JsonTransformer().Transform(transformer, input); - - Assert.AreEqual("{\"result\":{\"a\":true}}", result); - } - - [Test] - public void MultipleTransformsOverSelectedToken() - { - const string input = "{ \"select\": {\"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ]} }"; - const string transformer = - "{ \"result\": " + - "{ \"#transform($.select)\": [ " + - "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.d[0]),#currentvalue()),True,yes,no)\" } } }, " + - "{ \"condition\": \"#valueof($.condition)\" }," + - "{ \"a\": \"#exists($.condition[?(@.test=='yes')])\" } ] } }"; - - var result = new JsonTransformer().Transform(transformer, input); - - Assert.AreEqual("{\"result\":{\"a\":true}}", result); - } - - [Test] - public void MultipleTransformsWithinLoop() - { - const string input = "{ \"select\": [{ \"d\": [ \"one\", \"two\", \"three\" ], \"values\": [ \"z\", \"c\", \"n\" ] }, { \"d\": [ \"four\", \"five\", \"six\" ], \"values\": [ \"z\", \"c\", \"n\" ] }] }"; - const string transformer = - "{ \"result\": {" + - " \"#loop($.select,selectLoop)\": { " + - "\"#transform($)\": [ " + - "{ \"condition\": { \"#loop($.values)\": { \"test\": \"#ifcondition(#stringcontains(#currentvalueatpath($.d[0],selectLoop),#currentvalue()),True,yes,no)\" } } }, " + - "{ \"condition\": \"#valueof($.condition)\" }," + - "{ \"a\": \"#exists($.condition[?(@.test=='yes')])\" } ] " + - " } } }"; - - var result = new JsonTransformer().Transform(transformer, input); - - Assert.AreEqual("{\"result\":[{\"a\":true},{\"a\":false}]}", result); + Assert.AreEqual("{\"scalar\":true,\"object\":{\"result\":true},\"select_token\":{\"result\":true},\"loop\":[{\"result\":true},{\"result\":false}]}", result); } } } \ No newline at end of file From 6a0bfcbf6c01cf31d0e621c4d0cd8a43405e506d Mon Sep 17 00:00:00 2001 From: Courela Date: Fri, 7 Apr 2023 15:37:36 +0100 Subject: [PATCH 04/10] Update README.md --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/README.md b/README.md index 5ce91b4..0cc6f46 100644 --- a/README.md +++ b/README.md @@ -1586,6 +1586,8 @@ Output: ## Apply function over transformation Sometimes you cannnot achieve what you want directly from a single function (or composition). To overcome this you may want to apply a function over a previous transformation. That's what #applyover does. +You can also apply another complete transformation, not just a single function, over the transformed input. +Bare in mind that every special character (comma, parenthesis) must be escaped if they appear inside the second argument/transformation. Consider the following input: @@ -1612,6 +1614,90 @@ Output: ``` +## Multiple transformations + +The #applyover funnction is handy to make a simple transformation, but when extra transformation is complex, it can became cumbersome, because one has to escape all special characters. +To avoid this, there's a function called #transform. It takes a path as parameter, and like bulk functions, is composed by an array. Each element of the array is a transformation, +that will be applied over the generated result of the previous item of the array. The first item/transformation will be applied over the given input, or current element if one is on an array loop. +Note that for the second element/transformation and beyond, the input is the previous generated output of the previous transformation, so it's like a new transformation. + +Consider the following input: + +```JSON +{ + "spell": ["one", "two", "three"], + "letters": ["z", "c", "n"], + "nested": { + "spell": ["one", "two", "three"], + "letters": ["z", "c", "n"] + }, + "array": [{ + "spell": ["one", "two", "three"], + "letters": ["z", "c", "n"] + }, { + "spell": ["four", "five", "six"], + "letters": ["z", "c", "n"] + } + ] +} +``` + + +Transformer: + +```JSON +{ + "scalar": { + "#transform($)": [ + { "condition": { "#loop($.letters)": { "test": "#ifcondition(#stringcontains(#valueof($.spell[0]),#currentvalue()),True,yes,no)" } } }, + "#exists($.condition[?(@.test=='yes')])" + ] + }, + "object": { + "#transform($)": [ + { "condition": { "#loop($.letters)": { "test": "#ifcondition(#stringcontains(#valueof($.spell[0]),#currentvalue()),True,yes,no)" } } }, + { "intermediate_transform": "#valueof($.condition)" }, + { "result": "#exists($.intermediate_transform[?(@.test=='yes')])" } + ] + }, + "select_token": { + "#transform($.nested)": [ + { "condition": { "#loop($.letters)": { "test": "#ifcondition(#stringcontains(#valueof($.spell[0]),#currentvalue()),True,yes,no)" } } }, + { "intermediate_transform": "#valueof($.condition)" }, + { "result": "#exists($.intermediate_transform[?(@.test=='yes')])" } + ] + }, + "loop": { + "#loop($.array,selectLoop)": { + "#transform($)": [ + { "condition": { "#loop($.letters)": { "test": "#ifcondition(#stringcontains(#currentvalueatpath($.spell[0],selectLoop),#currentvalue()),True,yes,no)" } } }, + { "intermediate_transform": "#valueof($.condition)" }, + { "result": "#exists($.intermediate_transform[?(@.test=='yes')])" } + ] + } + } +} +``` + +Output: + +```JSON +{ + "scalar": true, + "object": { + "result": true + } + "select_token": { + "result": true + }, + "loop": [ + { "result": true }, + { "result": false } + ] +} +``` + + ## Schema Validation against multiple schemas using prefixes A new feature to validate a JSON against multiple schemas has been introduced in the new Nuget 2.0.xxx. This is to enable namespace based validation using prefixes like in XSD. From 56c78d6edf5ea35baba55611d61fce885b52fdb7 Mon Sep 17 00:00:00 2001 From: Courela Date: Fri, 7 Apr 2023 15:42:27 +0100 Subject: [PATCH 05/10] Revert "Replace operation using ISelectableToken; make static every bulk operation methods" This reverts commit e0b76ac2da5bc0244d75bff176cb63fda474456e. --- JUST.net/JsonTransformer.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/JUST.net/JsonTransformer.cs b/JUST.net/JsonTransformer.cs index 8144e7f..1718920 100644 --- a/JUST.net/JsonTransformer.cs +++ b/JUST.net/JsonTransformer.cs @@ -144,15 +144,15 @@ private void RecursiveEvaluate(ref JToken parentToken, IDictionary tokenToF } } - private static void CopyPostOperationBuildUp(JToken parentToken, List selectedTokens, JUSTContext context) + private void CopyPostOperationBuildUp(JToken parentToken, List selectedTokens) { foreach (JToken selectedToken in selectedTokens) { @@ -329,7 +329,7 @@ private static void CopyPostOperationBuildUp(JToken parentToken, List se { JObject parent = parentToken as JObject; JEnumerable copyChildren = selectedToken.Children(); - if (context.IsAddOrReplacePropertiesMode()) + if (Context.IsAddOrReplacePropertiesMode()) { CopyDescendants(parent, copyChildren); } @@ -391,12 +391,12 @@ private static void AddPostOperationBuildUp(JToken parentToken, List tok } } - private static void DeletePostOperationBuildUp(JToken parentToken, List tokensToDelete, JUSTContext context) + private void DeletePostOperationBuildUp(JToken parentToken, List tokensToDelete) { foreach (string selectedToken in tokensToDelete) { - JToken tokenToRemove = GetSelectableToken(parentToken, context).Select(selectedToken); + JToken tokenToRemove = GetSelectableToken(parentToken, Context).Select(selectedToken); if (tokenToRemove != null) tokenToRemove.Ancestors().First().Remove(); @@ -404,13 +404,12 @@ private static void DeletePostOperationBuildUp(JToken parentToken, List } - private static void ReplacePostOperationBuildUp(JToken parentToken, Dictionary tokensToReplace, JUSTContext context) + private static void ReplacePostOperationBuildUp(JToken parentToken, Dictionary tokensToReplace) { foreach (KeyValuePair tokenToReplace in tokensToReplace) { - JsonPathSelectable selectable = JsonTransformer.GetSelectableToken(parentToken, context); - JToken selectedToken = selectable.Select(tokenToReplace.Key); + JToken selectedToken = (parentToken as JObject).SelectToken(tokenToReplace.Key); selectedToken.Replace(tokenToReplace.Value); } From bc15ccc26073dd737ec96fec89db2364cf074296 Mon Sep 17 00:00:00 2001 From: Courela Date: Sat, 29 Apr 2023 21:56:39 +0100 Subject: [PATCH 06/10] Typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3dfd41..03db9b0 100644 --- a/README.md +++ b/README.md @@ -1636,7 +1636,7 @@ Output: ## Multiple transformations -The #applyover funnction is handy to make a simple transformation, but when extra transformation is complex, it can became cumbersome, because one has to escape all special characters. +The #applyover function is handy to make a simple transformation, but when extra transformation is complex, it can became cumbersome, because one has to escape all special characters. To avoid this, there's a function called #transform. It takes a path as parameter, and like bulk functions, is composed by an array. Each element of the array is a transformation, that will be applied over the generated result of the previous item of the array. The first item/transformation will be applied over the given input, or current element if one is on an array loop. Note that for the second element/transformation and beyond, the input is the previous generated output of the previous transformation, so it's like a new transformation. From 8b362ea19d7c2d31dd0f113d1313bd7afb469db6 Mon Sep 17 00:00:00 2001 From: Courela Date: Sat, 2 Dec 2023 10:05:32 +0000 Subject: [PATCH 07/10] #minatpath returns decimal.MaxValue if no valid number found (cherry picked from commit fc3cf5b68a9cb1fda98ffd5949f89fae3e583a44) --- JUST.net/Transformer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/JUST.net/Transformer.cs b/JUST.net/Transformer.cs index d2c724b..9469fd5 100644 --- a/JUST.net/Transformer.cs +++ b/JUST.net/Transformer.cs @@ -451,7 +451,7 @@ private static object Min(JToken token, JUSTContext context) public static object minatpath(JArray parsedArray, string path, JUSTContext context) { - decimal result = 0; + decimal? result = null; if (parsedArray != null) { @@ -461,12 +461,12 @@ public static object minatpath(JArray parsedArray, string path, JUSTContext cont if (selector.Select(path) is JToken selectedToken) { decimal thisValue = Convert.ToDecimal(selectedToken.ToString()); - result = Math.Min(result, thisValue); + result = Math.Min(result ?? decimal.MaxValue, thisValue); } } } - return TypedNumber(result); + return TypedNumber(result ?? decimal.MaxValue); } public static int arraylength(string array, JUSTContext context) From 197fcd00b08f60b9096a914360504f9ad637326f Mon Sep 17 00:00:00 2001 From: Courela Date: Sat, 6 Jan 2024 17:12:13 +0000 Subject: [PATCH 08/10] Initialize State with CurrentArrayToken and CurrentScopeToken values, to allow some already added keys to be present --- JUST.net/JsonTransformer.cs | 46 ++++++++++++++++++---------- JUST.net/State.cs | 11 +++++-- UnitTests/Arrays/LoopingTests.cs | 2 +- UnitTests/MultipleTransformations.cs | 6 ++-- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/JUST.net/JsonTransformer.cs b/JUST.net/JsonTransformer.cs index 2abbc47..91ba916 100644 --- a/JUST.net/JsonTransformer.cs +++ b/JUST.net/JsonTransformer.cs @@ -92,10 +92,10 @@ private JToken TransformValue(JToken transformer, JToken input) { var tmp = new JObject { - { "root", transformer } + { State.RootKey, transformer } }; Transform(tmp, input); - return tmp["root"]; + return tmp[State.RootKey]; } public JToken Transform(JObject transformer, string input) @@ -251,16 +251,16 @@ private void ParsePropertyFunction(State state, ref List loopProperties, isScope = true; break; case "transform": - TranformOperation(property, arguments, parentArray, currentArrayToken); + TranformOperation(property, arguments, state); break; } } - private void TranformOperation(JProperty property, string arguments, IDictionary parentArray, IDictionary currentArrayToken) + private void TranformOperation(JProperty property, string arguments, State state) { string[] argumentArr = ExpressionHelper.SplitArguments(arguments, Context.EscapeChar); - object functionResult = ParseArgument(null, parentArray, currentArrayToken, argumentArr[0]); + object functionResult = ParseArgument(state, argumentArr[0]); if (!(functionResult is string jsonPath)) { throw new ArgumentException($"Invalid path for #transform: '{argumentArr[0]}' resolved to null!"); @@ -270,18 +270,18 @@ private void TranformOperation(JProperty property, string arguments, IDictionary string alias = null; if (argumentArr.Length > 1) { - alias = ParseArgument(null, parentArray, currentArrayToken, argumentArr[1]) as string; - if (!(currentArrayToken?.ContainsKey(alias) ?? false)) + alias = ParseArgument(state, argumentArr[1]) as string; + if (!(state.CurrentArrayToken?.Any(t => t.Key.Key == alias) ?? false)) { throw new ArgumentException($"Unknown loop alias: '{argumentArr[1]}'"); } - JToken input = alias != null ? currentArrayToken?[alias] : currentArrayToken?.Last().Value ?? Context.Input; - var selectable = GetSelectableToken(currentArrayToken[alias], Context); + JToken input = alias != null ? state.CurrentArrayToken?.Single(t => t.Key.Key == alias).Value : state.CurrentArrayToken?.Last().Value ?? Context.Input; + var selectable = GetSelectableToken(state.CurrentArrayToken.Single(t => t.Key.Key == alias).Value, Context); selectedToken = selectable.Select(argumentArr[0]); } else { - var selectable = GetSelectableToken(currentArrayToken?.Last().Value ?? Context.Input, Context); + var selectable = GetSelectableToken(state.CurrentArrayToken?.Last().Value ?? Context.Input, Context); selectedToken = selectable.Select(argumentArr[0]); } @@ -294,12 +294,24 @@ private void TranformOperation(JProperty property, string arguments, IDictionary JToken token = property.Value[i]; if (token.Type == JTokenType.String) { - var obj = ParseFunction(token.Value(), null, parentArray, currentArrayToken); + var obj = ParseFunction( + token.Value(), + new State(token, Context.Input, _levelCounter, + state.CurrentArrayToken.Where(t => t.Key.Key != State.RootKey) + .ToDictionary(p => p.Key, p => p.Value), + state.CurrentScopeToken.Where(t => t.Key.Key != State.RootKey) + .ToDictionary(p => p.Key, p => p.Value))); token.Replace(GetToken(obj)); } else { - RecursiveEvaluate(ref token, i == 0 ? parentArray : null, i == 0 ? currentArrayToken : null); + RecursiveEvaluate( + ref token, + new State(token, Context.Input, _levelCounter, + state.CurrentArrayToken.Where(t => t.Key.Key != State.RootKey) + .ToDictionary(p => p.Key, p => p.Value), + state.CurrentScopeToken.Where(t => t.Key.Key != State.RootKey) + .ToDictionary(p => p.Key, p => p.Value))) } Context.Input = token; } @@ -524,7 +536,7 @@ private static void ScopePostOperationBuildUp(ref JToken parentToken, List loopProperties, ref JArray arrayToForm, ref JObject dictToForm, JToken childToken) { var args = ExpressionHelper.SplitArguments(arguments, Context.EscapeChar); - var previousAlias = "root"; + var previousAlias = State.RootKey; args[0] = (string)ParseFunction(args[0], state); _levelCounter++; string alias = args.Length > 1 ? (string)ParseFunction(args[1].Trim(), state) : $"loop{_levelCounter}"; @@ -532,7 +544,7 @@ private void LoopOperation(string propertyName, string arguments, State state, r if (args.Length > 2) { previousAlias = (string)ParseFunction(args[2].Trim(), state); - state.CurrentArrayToken.Add(new LevelKey() { Key = previousAlias, Level = _levelCounter }, Context.Input); + state.CurrentArrayToken.Add(new LevelKey() { Key = alias, Level = _levelCounter }, Context.Input); } else if (state.CurrentArrayToken.Any(t => t.Key.Key == alias)) { @@ -642,7 +654,7 @@ private bool IsArray(JToken arrayToken, string strArrayToken, State state, strin private void ScopeOperation(string propertyName, string arguments, State state, ref List scopeProperties, ref JObject scopeToForm, JToken childToken) { var args = ExpressionHelper.SplitArguments(arguments, Context.EscapeChar); - var previousAlias = "root"; + var previousAlias = State.RootKey; args[0] = (string)ParseFunction(args[0], state); _levelCounter++; string alias = args.Length > 1 ? (string)ParseFunction(args[1].Trim(), state) : $"scope{_levelCounter}"; @@ -992,10 +1004,10 @@ private object ParseApplyOver(IList listParameters, State state) IDictionary tmpScope = new Dictionary(state.CurrentScopeToken); state.CurrentArrayToken.Clear(); - state.CurrentArrayToken.Add(new LevelKey { Key = "root", Level = 0 }, input); + state.CurrentArrayToken.Add(new LevelKey { Key = State.RootKey, Level = 0 }, input); state.CurrentScopeToken.Clear(); - state.CurrentScopeToken.Add(new LevelKey { Key = "root", Level = 0 }, input); + state.CurrentScopeToken.Add(new LevelKey { Key = State.RootKey, Level = 0 }, input); if (listParameters.ElementAt(1).ToString().Trim().Trim('\'').StartsWith("{")) { diff --git a/JUST.net/State.cs b/JUST.net/State.cs index ebeea91..14fd00b 100644 --- a/JUST.net/State.cs +++ b/JUST.net/State.cs @@ -10,12 +10,17 @@ internal struct LevelKey internal sealed class State { - internal State(JToken transformer, JToken input, int levelCounter) + internal const string RootKey = "root"; + internal State(JToken transformer, JToken input, int levelCounter, + IDictionary currentArrayToken = null, + IDictionary currentScopeToken = null) { Transformer = transformer; ParentArray = new Dictionary(); - CurrentArrayToken = new Dictionary { { new LevelKey { Level = levelCounter, Key = "root"}, input } }; - CurrentScopeToken = new Dictionary { { new LevelKey { Level = levelCounter, Key = "root"}, input } }; + CurrentArrayToken = new Dictionary { { new LevelKey { Level = levelCounter, Key = State.RootKey}, input } } + .Concat(currentArrayToken ?? new Dictionary()).ToDictionary(p => p.Key, p => p.Value); + CurrentScopeToken = new Dictionary { { new LevelKey { Level = levelCounter, Key = State.RootKey}, input } } + .Concat(currentScopeToken ?? new Dictionary()).ToDictionary(p => p.Key, p => p.Value); } internal JToken Transformer { get; private set; } internal IDictionary ParentArray { get; private set; } diff --git a/UnitTests/Arrays/LoopingTests.cs b/UnitTests/Arrays/LoopingTests.cs index 65ec48e..d602e93 100644 --- a/UnitTests/Arrays/LoopingTests.cs +++ b/UnitTests/Arrays/LoopingTests.cs @@ -360,7 +360,7 @@ public void LoopPathRefersAnotherLoopAlias() const string input = "{ \"orderItems\": [ { \"id\": \"1\", \"sku\": \"a\" }, { \"id\": \"2\", \"sku\": \"b\" }, { \"id\": \"3\", \"sku\": \"c\" } ], \"affectedItems\": [ 1, 2 ], \"test\": \"abc\" }"; const string transformer = "{ \"#loop($.affectedItems,affectedItems)\": { \"#loop($.orderItems,orderItems,root)\": { \"sku\": \"#currentvalueatpath($.sku)\", \"id\": \"#currentvalue(affectedItems)\" } }}"; - var result = new JsonTransformer().Transform(transformer, input); + var result = new JsonTransformer(new JUSTContext() { EvaluationMode = EvaluationMode.Strict }).Transform(transformer, input); Assert.AreEqual("[[{\"sku\":\"a\",\"id\":1},{\"sku\":\"b\",\"id\":1},{\"sku\":\"c\",\"id\":1}],[{\"sku\":\"a\",\"id\":2},{\"sku\":\"b\",\"id\":2},{\"sku\":\"c\",\"id\":2}]]", result); } diff --git a/UnitTests/MultipleTransformations.cs b/UnitTests/MultipleTransformations.cs index b0fd8e2..fbfb59b 100644 --- a/UnitTests/MultipleTransformations.cs +++ b/UnitTests/MultipleTransformations.cs @@ -16,7 +16,7 @@ public void MultipleTransformsScalarResult() "{ \"intermediate_transform\": \"#valueof($.condition)\" }," + "\"#exists($.intermediate_transform[?(@.test=='yes')])\" ] } }"; - var result = new JsonTransformer().Transform(transformer, input); + var result = new JsonTransformer(new JUSTContext() { EvaluationMode = EvaluationMode.Strict}).Transform(transformer, input); Assert.AreEqual("{\"result\":true}", result); } @@ -48,7 +48,7 @@ public void MultipleTransformsOverSelectedToken() "{ \"intermediate_transform\": \"#valueof($.condition)\" }," + "{ \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] } }"; - var result = new JsonTransformer().Transform(transformer, input); + var result = new JsonTransformer(new JUSTContext() { EvaluationMode = EvaluationMode.Strict}).Transform(transformer, input); Assert.AreEqual("{\"select_token\":{\"result\":true}}", result); } @@ -66,7 +66,7 @@ public void MultipleTransformsWithinLoop() "{ \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] " + " } } }"; - var result = new JsonTransformer().Transform(transformer, input); + var result = new JsonTransformer(new JUSTContext() { EvaluationMode = EvaluationMode.Strict}).Transform(transformer, input); Assert.AreEqual("{\"loop\":[{\"result\":true},{\"result\":false}]}", result); } From ae89d08d4ef4fb6284dc53526ca9057e0d2ab234 Mon Sep 17 00:00:00 2001 From: Courela Date: Sat, 17 Feb 2024 12:52:12 +0000 Subject: [PATCH 09/10] ComparisonHelper ignoring case if one of the values is of type a boolean; TODO for input in transform --- JUST.net/ComparisonHelper.cs | 6 +++--- JUST.net/JsonTransformer.cs | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/JUST.net/ComparisonHelper.cs b/JUST.net/ComparisonHelper.cs index 7657fff..2862697 100644 --- a/JUST.net/ComparisonHelper.cs +++ b/JUST.net/ComparisonHelper.cs @@ -6,7 +6,7 @@ internal static class ComparisonHelper { public static bool Equals(object x, object y, EvaluationMode evaluationMode) { - var comparisonType = (evaluationMode == EvaluationMode.Strict) + var comparisonType = (evaluationMode == EvaluationMode.Strict && x.GetType() != typeof(bool) && y.GetType() != typeof(bool)) ? StringComparison.CurrentCulture : StringComparison.InvariantCultureIgnoreCase; @@ -15,11 +15,11 @@ public static bool Equals(object x, object y, EvaluationMode evaluationMode) public static bool Contains(object x, object y, EvaluationMode evaluationMode) { - var comparisonType = (evaluationMode == EvaluationMode.Strict) + var comparisonType = (evaluationMode == EvaluationMode.Strict && x.GetType() != typeof(bool) && y.GetType() != typeof(bool)) ? StringComparison.CurrentCulture : StringComparison.InvariantCultureIgnoreCase; - return ((x != null) && x.ToString().IndexOf(y?.ToString() ?? string.Empty, comparisonType) >= 0); + return x != null && x.ToString().IndexOf(y?.ToString() ?? string.Empty, comparisonType) >= 0; } } } diff --git a/JUST.net/JsonTransformer.cs b/JUST.net/JsonTransformer.cs index 91ba916..a23691d 100644 --- a/JUST.net/JsonTransformer.cs +++ b/JUST.net/JsonTransformer.cs @@ -311,7 +311,7 @@ private void TranformOperation(JProperty property, string arguments, State state state.CurrentArrayToken.Where(t => t.Key.Key != State.RootKey) .ToDictionary(p => p.Key, p => p.Value), state.CurrentScopeToken.Where(t => t.Key.Key != State.RootKey) - .ToDictionary(p => p.Key, p => p.Value))) + .ToDictionary(p => p.Key, p => p.Value))); } Context.Input = token; } @@ -1097,7 +1097,7 @@ private object GetFunctionOutput(string functionName, IList listParamete null, "JUST.Transformer`1", functionName, - new[] { state.ParentArray.Single(p => p.Key.Key == alias).Value, state.CurrentArrayToken.Single(p => p.Key.Key == alias).Value }.Concat(listParameters.ToArray()).ToArray(), + new[] { state.ParentArray.First(p => p.Key.Key == alias || p.Key.Key == state.ParentArray.Last().Key.Key) .Value, state.CurrentArrayToken.Single(p => p.Key.Key == alias).Value }.Concat(listParameters.ToArray()).ToArray(), convertParameters, Context); } @@ -1138,11 +1138,19 @@ private object GetFunctionOutput(string functionName, IList listParamete { if (functionName != "valueof") { - ((JUSTContext)listParameters.Last()).Input = state.CurrentArrayToken.Last().Value; + // TODO Input must change to previous transformed output + // if (state.Transformer != null) + // { + // ((JUSTContext)listParameters.Last()).Input = state.CurrentScopeToken.Last().Value; + // } + // else + // { + ((JUSTContext)listParameters.Last()).Input = state.CurrentArrayToken.Last().Value; + // } } else { - if (functionName == "valueof" && listParameters.Count > 2) + if (listParameters.Count > 2) { ((JUSTContext)listParameters.Last()).Input = state.CurrentScopeToken.Single(p => p.Key.Key == listParameters[1].ToString()).Value; listParameters.Remove(listParameters.ElementAt(listParameters.Count - 2)); From cf6dad26c5ca2c637e7081b16bdc06086dba2a7a Mon Sep 17 00:00:00 2001 From: Courela Date: Thu, 28 Mar 2024 00:06:09 +0000 Subject: [PATCH 10/10] Resolve loops inside of #Transform --- JUST.net/JsonTransformer.cs | 19 +++++++++---------- JUST.net/State.cs | 5 ++++- UnitTests/ReadmeTests.cs | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/JUST.net/JsonTransformer.cs b/JUST.net/JsonTransformer.cs index a23691d..a724052 100644 --- a/JUST.net/JsonTransformer.cs +++ b/JUST.net/JsonTransformer.cs @@ -300,7 +300,7 @@ private void TranformOperation(JProperty property, string arguments, State state state.CurrentArrayToken.Where(t => t.Key.Key != State.RootKey) .ToDictionary(p => p.Key, p => p.Value), state.CurrentScopeToken.Where(t => t.Key.Key != State.RootKey) - .ToDictionary(p => p.Key, p => p.Value))); + .ToDictionary(p => p.Key, p => p.Value), true)); token.Replace(GetToken(obj)); } else @@ -311,7 +311,7 @@ private void TranformOperation(JProperty property, string arguments, State state state.CurrentArrayToken.Where(t => t.Key.Key != State.RootKey) .ToDictionary(p => p.Key, p => p.Value), state.CurrentScopeToken.Where(t => t.Key.Key != State.RootKey) - .ToDictionary(p => p.Key, p => p.Value))); + .ToDictionary(p => p.Key, p => p.Value), true)); } Context.Input = token; } @@ -1138,15 +1138,14 @@ private object GetFunctionOutput(string functionName, IList listParamete { if (functionName != "valueof") { - // TODO Input must change to previous transformed output - // if (state.Transformer != null) - // { - // ((JUSTContext)listParameters.Last()).Input = state.CurrentScopeToken.Last().Value; - // } - // else - // { + if (state.Multiple) + { + ((JUSTContext)listParameters.Last()).Input = state.CurrentScopeToken.Last().Value; + } + else + { ((JUSTContext)listParameters.Last()).Input = state.CurrentArrayToken.Last().Value; - // } + } } else { diff --git a/JUST.net/State.cs b/JUST.net/State.cs index 14fd00b..00e25f3 100644 --- a/JUST.net/State.cs +++ b/JUST.net/State.cs @@ -13,7 +13,8 @@ internal sealed class State internal const string RootKey = "root"; internal State(JToken transformer, JToken input, int levelCounter, IDictionary currentArrayToken = null, - IDictionary currentScopeToken = null) + IDictionary currentScopeToken = null, + bool multiple = false) { Transformer = transformer; ParentArray = new Dictionary(); @@ -21,11 +22,13 @@ internal State(JToken transformer, JToken input, int levelCounter, .Concat(currentArrayToken ?? new Dictionary()).ToDictionary(p => p.Key, p => p.Value); CurrentScopeToken = new Dictionary { { new LevelKey { Level = levelCounter, Key = State.RootKey}, input } } .Concat(currentScopeToken ?? new Dictionary()).ToDictionary(p => p.Key, p => p.Value); + Multiple = multiple; } internal JToken Transformer { get; private set; } internal IDictionary ParentArray { get; private set; } internal IDictionary CurrentArrayToken { get; private set; } internal IDictionary CurrentScopeToken { get; private set; } + internal bool Multiple { get; private set; } internal string GetHigherAlias() { diff --git a/UnitTests/ReadmeTests.cs b/UnitTests/ReadmeTests.cs index df8f0ba..fe03105 100644 --- a/UnitTests/ReadmeTests.cs +++ b/UnitTests/ReadmeTests.cs @@ -274,7 +274,7 @@ public void Transform() const string input = "{ \"spell\": [\"one\", \"two\", \"three\"], \"letters\": [\"z\", \"c\", \"n\"], \"nested\": { \"spell\": [\"one\", \"two\", \"three\"], \"letters\": [\"z\", \"c\", \"n\"] },\"array\": [{ \"spell\": [\"one\", \"two\", \"three\"], \"letters\": [\"z\", \"c\", \"n\"] }, { \"spell\": [\"four\", \"five\", \"six\"], \"letters\": [\"z\", \"c\", \"n\"] } ]}"; const string transformer = "{ \"scalar\": { \"#transform($)\": [{ \"condition\": { \"#loop($.letters)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.spell[0]),#currentvalue()),True,yes,no)\" } } }, \"#exists($.condition[?(@.test=='yes')])\"] }, \"object\": { \"#transform($)\": [{ \"condition\": { \"#loop($.letters)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.spell[0]),#currentvalue()),True,yes,no)\" } } }, { \"intermediate_transform\": \"#valueof($.condition)\" }, { \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] }, \"select_token\": { \"#transform($.nested)\": [{ \"condition\": { \"#loop($.letters)\": { \"test\": \"#ifcondition(#stringcontains(#valueof($.spell[0]),#currentvalue()),True,yes,no)\" } } }, { \"intermediate_transform\": \"#valueof($.condition)\" }, { \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] }, \"loop\": { \"#loop($.array,selectLoop)\": { \"#transform($)\": [{ \"condition\": { \"#loop($.letters)\": { \"test\": \"#ifcondition(#stringcontains(#currentvalueatpath($.spell[0],selectLoop),#currentvalue()),True,yes,no)\" } } }, { \"intermediate_transform\": \"#valueof($.condition)\" }, { \"result\": \"#exists($.intermediate_transform[?(@.test=='yes')])\" } ] } } } "; - var result = new JsonTransformer().Transform(transformer, input); + var result = new JsonTransformer(new JUSTContext { EvaluationMode = EvaluationMode.Strict }).Transform(transformer, input); Assert.AreEqual("{\"scalar\":true,\"object\":{\"result\":true},\"select_token\":{\"result\":true},\"loop\":[{\"result\":true},{\"result\":false}]}", result); }