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/JUST.net/JsonTransformer.cs b/JUST.net/JsonTransformer.cs
index 0530cf7..cbd1861 100644
--- a/JUST.net/JsonTransformer.cs
+++ b/JUST.net/JsonTransformer.cs
@@ -230,7 +230,63 @@ 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);
+ break;
+ }
+ }
+
+ 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];
+ if (token.Type == JTokenType.String)
+ {
+ var obj = ParseFunction(token.Value(), parentArray, currentArrayToken);
+ token.Replace(GetToken(obj));
+ }
+ else
+ {
+ RecursiveEvaluate(ref token, i == 0 ? parentArray : null, i == 0 ? currentArrayToken : null);
+ }
+ Context.Input = token;
+ }
+
+ Context.Input = originalInput;
}
+ property.Parent.Replace(property.Value[property.Value.Count() - 1]);
}
private void PostOperationsBuildUp(ref JToken parentToken, List tokenToForm)
@@ -902,12 +958,12 @@ private object ParseApplyOver(IDictionary array, IDictionary 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.
-First argument is the first transformation to apply to input, and the result will serve as input to the second argument/transformation. Second argument can be a simple function or a complex transformation (an object or an array).
-Note that if any of the arguments/transformations of #applyover has commas (,), one has to use #constant_comma to represent them (and use #xconcat to construct the argument/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. First argument is the first transformation to apply to input, and the result will serve as input to the second argument/transformation. Second argument can be a simple function or a complex transformation (an object or an array).
+Bare in mind that every special character (comma, parenthesis) must be escaped if they appear inside the second argument/transformation.
Consider the following input:
@@ -1635,6 +1634,90 @@ Output:
```
+## Multiple transformations
+
+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.
+
+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.
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
diff --git a/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj b/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj
index d9362c8..f172e26 100644
--- a/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj
+++ b/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj
@@ -9,9 +9,9 @@
-
-
-
+
+
+
diff --git a/UnitTests/JUST.net.UnitTests.csproj b/UnitTests/JUST.net.UnitTests.csproj
index 0d6afec..7b76ec2 100644
--- a/UnitTests/JUST.net.UnitTests.csproj
+++ b/UnitTests/JUST.net.UnitTests.csproj
@@ -9,9 +9,9 @@
-
-
-
+
+
+
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 a846abb..d6aac03 100644
--- a/UnitTests/ReadmeTests.cs
+++ b/UnitTests/ReadmeTests.cs
@@ -256,5 +256,16 @@ 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 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);
+
+ Assert.AreEqual("{\"scalar\":true,\"object\":{\"result\":true},\"select_token\":{\"result\":true},\"loop\":[{\"result\":true},{\"result\":false}]}", result);
+ }
}
}
\ No newline at end of file