From e0b76ac2da5bc0244d75bff176cb63fda474456e Mon Sep 17 00:00:00 2001 From: Courela Date: Sun, 26 Mar 2023 16:16:06 +0100 Subject: [PATCH 01/10] Replace operation using ISelectableToken; make static every bulk operation methods (cherry picked from commit a1ba2f71f2c99eb21c07a724679c37c9c1a5ab9a) --- JUST.net/JsonTransformer.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/JUST.net/JsonTransformer.cs b/JUST.net/JsonTransformer.cs index 5fe4344..4dc8b64 100644 --- a/JUST.net/JsonTransformer.cs +++ b/JUST.net/JsonTransformer.cs @@ -144,15 +144,15 @@ private void RecursiveEvaluate(ref JToken parentToken, IDictionary tokenToF } } - private void CopyPostOperationBuildUp(JToken parentToken, List selectedTokens) + private static void CopyPostOperationBuildUp(JToken parentToken, List selectedTokens, JUSTContext context) { foreach (JToken selectedToken in selectedTokens) { @@ -273,7 +273,7 @@ private void CopyPostOperationBuildUp(JToken parentToken, List selectedT { JObject parent = parentToken as JObject; JEnumerable copyChildren = selectedToken.Children(); - if (Context.IsAddOrReplacePropertiesMode()) + if (context.IsAddOrReplacePropertiesMode()) { CopyDescendants(parent, copyChildren); } @@ -335,12 +335,12 @@ private static void AddPostOperationBuildUp(JToken parentToken, List tok } } - private void DeletePostOperationBuildUp(JToken parentToken, List tokensToDelete) + private static void DeletePostOperationBuildUp(JToken parentToken, List tokensToDelete, JUSTContext context) { 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(); @@ -348,12 +348,13 @@ private void DeletePostOperationBuildUp(JToken parentToken, List tokensT } - private static void ReplacePostOperationBuildUp(JToken parentToken, Dictionary tokensToReplace) + private static void ReplacePostOperationBuildUp(JToken parentToken, Dictionary tokensToReplace, JUSTContext context) { foreach (KeyValuePair tokenToReplace in tokensToReplace) { - JToken selectedToken = (parentToken as JObject).SelectToken(tokenToReplace.Key); + JsonPathSelectable selectable = JsonTransformer.GetSelectableToken(parentToken, context); + JToken selectedToken = selectable.Select(tokenToReplace.Key); selectedToken.Replace(tokenToReplace.Value); } From c8cc44ceb0f3aa6e9b049a75b63c90f10062b804 Mon Sep 17 00:00:00 2001 From: Courela Date: Sun, 26 Mar 2023 16:35:02 +0100 Subject: [PATCH 02/10] Update test nuget packages: nunit, NUnit3TestAdapter and Microsoft.NET.Test.Sdk --- UnitTests/JUST.net.UnitTests.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UnitTests/JUST.net.UnitTests.csproj b/UnitTests/JUST.net.UnitTests.csproj index 7729373..7b76ec2 100644 --- a/UnitTests/JUST.net.UnitTests.csproj +++ b/UnitTests/JUST.net.UnitTests.csproj @@ -9,9 +9,9 @@ - - - + + + From 6c794fb758cabf50a509d0d320e984ba2abc442d Mon Sep 17 00:00:00 2001 From: Courela Date: Sun, 26 Mar 2023 16:41:53 +0100 Subject: [PATCH 03/10] TargetFramework; change UnitTestForExternalAssemblyBug project to use latest versions of nunit, test adapter and test sdk --- JUST.net/JUST.net.csproj | 2 +- .../UnitTestForExternalAssemblyBug.csproj | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/JUST.net/JUST.net.csproj b/JUST.net/JUST.net.csproj index 5b771ab..0d2ee34 100644 --- a/JUST.net/JUST.net.csproj +++ b/JUST.net/JUST.net.csproj @@ -2,7 +2,7 @@ JUST - JSON Under Simple Transformation - netstandard2.0; + netstandard2.0 This a cool .NET Standard library which enables you to transform a JSON document into another JSON document using JSON transformations using JSON path. This is the JSON equivalent of XSLT. This will repace the JUST and JUST.NETCore packages. This is the JSON equivalent of XSLT diff --git a/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj b/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj index d255ec2..f172e26 100644 --- a/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj +++ b/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj @@ -9,9 +9,9 @@ - - - + + + From 2a81b1160e423e65d2ca0080a2631d55246ca87b Mon Sep 17 00:00:00 2001 From: Courela Date: Sun, 26 Mar 2023 16:44:11 +0100 Subject: [PATCH 04/10] Fix build of UnitTestForExternalAssemblyBug project --- UnitTestForExternalAssemblyBug/ExternalAssemblyBugTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/UnitTestForExternalAssemblyBug/ExternalAssemblyBugTests.cs b/UnitTestForExternalAssemblyBug/ExternalAssemblyBugTests.cs index b45aa6d..3923d1d 100644 --- a/UnitTestForExternalAssemblyBug/ExternalAssemblyBugTests.cs +++ b/UnitTestForExternalAssemblyBug/ExternalAssemblyBugTests.cs @@ -1,9 +1,7 @@ using System; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.Loader; -using Microsoft.Extensions.DependencyModel.Resolution; using NUnit.Framework; namespace JUST.UnitTests From 9c79c8ccafcccd571bdf0a253c81bde30b042edf Mon Sep 17 00:00:00 2001 From: Courela Date: Mon, 3 Apr 2023 00:31:44 +0100 Subject: [PATCH 05/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 06/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 07/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 08/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 09/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 10/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.