diff --git a/JUST.net/DataTransformer.cs b/JUST.net/DataTransformer.cs index b4be945..2e983f8 100644 --- a/JUST.net/DataTransformer.cs +++ b/JUST.net/DataTransformer.cs @@ -157,8 +157,6 @@ private object EvaluateFunction(string functionString, string inputJson, JArray } } - listParameters.Add(new JUSTContext(inputJson)); - var parameters = listParameters.ToArray(); var convertParameters = true; if (new[] { "concat", "xconcat", "currentproperty" }.Contains(functionName)) { @@ -167,7 +165,7 @@ private object EvaluateFunction(string functionString, string inputJson, JArray if (functionName == "loop") { - output = GetLoopResult(parameters, loopArgumentString); + output = GetLoopResult(listParameters.Concat(new object[] { JToken.Parse(inputJson), Context }).ToArray(), loopArgumentString, Context); } else if (functionName == "currentvalue" || functionName == "currentindex" || functionName == "lastindex" || functionName == "lastvalue") @@ -175,11 +173,11 @@ private object EvaluateFunction(string functionString, string inputJson, JArray else if (functionName == "currentvalueatpath" || functionName == "lastvalueatpath") output = Caller("JUST.Transformer`1", functionName, new object[] { array, currentArrayElement, arguments[0], new JUSTContext() }); else if (functionName == "customfunction") - output = CallCustomFunction(parameters); + output = CallCustomFunction(listParameters.Concat(new object[] { JToken.Parse(inputJson), Context }).ToArray()); else if (Context?.IsRegisteredCustomFunction(functionName) ?? false) { var methodInfo = Context.GetCustomMethod(functionName); - output = ReflectionHelper.InvokeCustomMethod(methodInfo, parameters, convertParameters, Context); + output = ReflectionHelper.InvokeCustomMethod(methodInfo, listParameters.Concat(new object[] { JToken.Parse(inputJson), Context }).ToArray(), convertParameters, Context); } else if (functionName == "xconcat" || functionName == "xadd" || functionName == "mathequals" || functionName == "mathgreaterthan" || functionName == "mathlessthan" || functionName == "mathgreaterthanorequalto" @@ -187,24 +185,24 @@ private object EvaluateFunction(string functionString, string inputJson, JArray functionName == "stringequals") { object[] oParams = new object[1]; - oParams[0] = parameters; + oParams[0] = listParameters.Concat(new object[] { Context }).ToArray(); output = Caller("JUST.Transformer`1", functionName, oParams); } else - output = Caller("JUST.Transformer`1", functionName, parameters); + output = Caller("JUST.Transformer`1", functionName, listParameters.Concat(new object[] { JToken.Parse(inputJson), Context }).ToArray()); } return output; } - private string GetLoopResult(object[] parameters,string loopArgumentString) + private string GetLoopResult(object[] parameters,string loopArgumentString, JUSTContext context) { string returnString = string.Empty; - if (parameters.Length < 2) + if (parameters.Length < 3) throw new Exception("Incorrect number of parameters for function #Loop"); - var context = (JUSTContext)parameters[parameters.Length - 1]; - JToken token = context.Input; + //var context = (JUSTContext)parameters[parameters.Length - 1]; + JToken token = (JToken)parameters[parameters.Length - 2]; JToken selectedToken = context.Resolve(token).Select(parameters[0].ToString()); if (selectedToken.Type != JTokenType.Array) @@ -214,7 +212,7 @@ private string GetLoopResult(object[] parameters,string loopArgumentString) string seperator = Environment.NewLine; - if (parameters.Length == 3) + if (parameters.Length == 4) seperator = parameters[1].ToString(); foreach (JToken arrToken in selectedToken.Children()) diff --git a/JUST.net/ExceptionHelper.cs b/JUST.net/ExceptionHelper.cs index acd5375..f79e9eb 100644 --- a/JUST.net/ExceptionHelper.cs +++ b/JUST.net/ExceptionHelper.cs @@ -4,9 +4,9 @@ namespace JUST { internal static class ExceptionHelper { - internal static void HandleException(Exception ex, EvaluationMode evaluationMode) + internal static void HandleException(Exception ex, bool IsStrictMode) { - if ( (evaluationMode & EvaluationMode.Strict) == EvaluationMode.Strict) + if (IsStrictMode) { if (ex.InnerException != null) { diff --git a/JUST.net/IContext.cs b/JUST.net/IContext.cs new file mode 100644 index 0000000..f76d27e --- /dev/null +++ b/JUST.net/IContext.cs @@ -0,0 +1,10 @@ +using JUST; +using JUST.net.Selectables; +using Newtonsoft.Json.Linq; + +public interface IContext{ + char SplitGroupChar { get; } + int DefaultDecimalPlaces { get; } + bool IsStrictMode(); + T Resolve(JToken token) where T: ISelectableToken; +} \ No newline at end of file diff --git a/JUST.net/JUSTContext.cs b/JUST.net/JUSTContext.cs index 77ef864..1a8e733 100644 --- a/JUST.net/JUSTContext.cs +++ b/JUST.net/JUSTContext.cs @@ -34,13 +34,11 @@ public enum EvaluationMode : short Strict = 4 } - public class JUSTContext + public class JUSTContext : IContext { private Dictionary _customFunctions = new Dictionary(); private int _defaultDecimalPlaces = 28; - internal JToken Input; - public EvaluationMode EvaluationMode = EvaluationMode.FallbackToDefault; private char _escapeChar = '/'; //do not use backslash, it is already the escape char in JSON private char _splitGroupChar = ':'; @@ -88,12 +86,16 @@ public JUSTContext(IEnumerable customFunctions) } } - internal JUSTContext(string inputJson) + internal JUSTContext(JUSTContext context) { - Input = JToken.Parse(inputJson); + this.EvaluationMode = context.EvaluationMode; + this.EscapeChar = context.EscapeChar; + this.DefaultDecimalPlaces = context.DefaultDecimalPlaces; + this.SplitGroupChar = context.SplitGroupChar; + this._customFunctions = context._customFunctions; } - internal bool IsStrictMode() + public bool IsStrictMode() { return (EvaluationMode & EvaluationMode.Strict) == EvaluationMode.Strict; } @@ -148,7 +150,7 @@ internal bool IsRegisteredCustomFunction(string aliasOrName) return _customFunctions.ContainsKey(aliasOrName); } - internal T Resolve(JToken token) where T: ISelectableToken + public T Resolve(JToken token) where T: ISelectableToken { T instance = Activator.CreateInstance(); instance.Token = token; diff --git a/JUST.net/JsonTransformer.cs b/JUST.net/JsonTransformer.cs index 0530cf7..26a4e7a 100644 --- a/JUST.net/JsonTransformer.cs +++ b/JUST.net/JsonTransformer.cs @@ -105,16 +105,15 @@ public JToken Transform(JObject transformer, string input) public JToken Transform(JObject transformer, JToken input) { - Context.Input = input; var parentToken = (JToken)transformer; - RecursiveEvaluate(ref parentToken, null, null); + RecursiveEvaluate(ref parentToken, null, null, input); return parentToken; } #region RecursiveEvaluate - private void RecursiveEvaluate(ref JToken parentToken, IDictionary parentArray, IDictionary currentArrayToken) + private void RecursiveEvaluate(ref JToken parentToken, IDictionary parentArray, IDictionary currentArrayToken, JToken input) { if (parentToken == null) { @@ -123,75 +122,62 @@ private void RecursiveEvaluate(ref JToken parentToken, IDictionary tokens = parentToken.Children(); - List selectedTokens = null; - Dictionary tokensToReplace = null; - List tokensToDelete = null; - - List loopProperties = null; - List condProps = null; - JArray arrayToForm = null; - JObject dictToForm = null; - List tokenToForm = null; - List tokensToAdd = null; - - bool isLoop = false; - bool isBulk = false; - + TransformHelper helper = new TransformHelper(); foreach (JToken childToken in tokens) { - ParseToken(parentToken, parentArray, currentArrayToken, ref selectedTokens, ref tokensToReplace, ref tokensToDelete, ref loopProperties, ref condProps, ref arrayToForm, ref dictToForm, ref tokenToForm, ref tokensToAdd, ref isLoop, ref isBulk, childToken); + ParseToken(parentToken, parentArray, currentArrayToken, helper, childToken, input); } - if (selectedTokens != null) + if (helper.selectedTokens != null) { - CopyPostOperationBuildUp(parentToken, selectedTokens); + CopyPostOperationBuildUp(parentToken, helper.selectedTokens); } - if (tokensToReplace != null) + if (helper.tokensToReplace != null) { - ReplacePostOperationBuildUp(parentToken, tokensToReplace); + ReplacePostOperationBuildUp(parentToken, helper.tokensToReplace); } - if (tokensToDelete != null) + if (helper.tokensToDelete != null) { - DeletePostOperationBuildUp(parentToken, tokensToDelete); + DeletePostOperationBuildUp(parentToken, helper.tokensToDelete); } - if (tokensToAdd != null) + if (helper.tokensToAdd != null) { - AddPostOperationBuildUp(parentToken, tokensToAdd); + AddPostOperationBuildUp(parentToken, helper.tokensToAdd); } - PostOperationsBuildUp(ref parentToken, tokenToForm); - if (loopProperties != null || condProps != null) + PostOperationsBuildUp(ref parentToken, helper.tokenToForm); + if (helper.loopProperties != null || helper.condProps != null) { - LoopPostOperationBuildUp(ref parentToken, condProps, loopProperties, arrayToForm, dictToForm); + LoopPostOperationBuildUp(ref parentToken, helper); } } - private void ParseToken(JToken parentToken, IDictionary parentArray, IDictionary currentArrayToken, ref List selectedTokens, ref Dictionary tokensToReplace, ref List tokensToDelete, ref List loopProperties, ref List condProps, ref JArray arrayToForm, ref JObject dictToForm, ref List tokenToForm, ref List tokensToAdd, ref bool isLoop, ref bool isBulk, JToken childToken) + private void ParseToken(JToken parentToken, IDictionary parentArray, IDictionary currentArrayToken, TransformHelper helper, JToken childToken, JToken input) { if (childToken.Type == JTokenType.Array && (parentToken as JProperty)?.Name.Trim() != "#") { - IEnumerable itemsToAdd = TransformArray(childToken.Children(), parentArray, currentArrayToken); + IEnumerable itemsToAdd = TransformArray(childToken.Children(), parentArray, currentArrayToken, input); BuildArrayToken(childToken as JArray, itemsToAdd); } else if (childToken.Type == JTokenType.Property && childToken is JProperty property && property.Name != null) { /* For looping*/ - isLoop = false; + helper.isLoop = false; if (property.Name == "#" && property.Value.Type == JTokenType.Array && property.Value is JArray values) { - BulkOperations(values.Children(), parentArray, currentArrayToken, ref selectedTokens, ref tokensToReplace, ref tokensToDelete); - isBulk = true; + BulkOperations(values.Children(), parentArray, currentArrayToken, helper, input); + helper.isBulk = true; } else { - isBulk = false; + helper.isBulk = false; if (ExpressionHelper.TryParseFunctionNameAndArguments(property.Name, out string functionName, out string arguments)) { - ParsePropertyFunction(parentArray, currentArrayToken, ref loopProperties, ref condProps, ref arrayToForm, ref dictToForm, ref tokenToForm, ref tokensToAdd, ref isLoop, childToken, property, functionName, arguments); + ParsePropertyFunction(parentArray, currentArrayToken, helper, childToken, property, functionName, arguments, input); } else if (property.Value.ToString().Trim().StartsWith("#")) { - property.Value = GetToken(ParseFunction(property.Value.ToString().Trim(), parentArray, currentArrayToken)); + property.Value = GetToken(ParseFunction(property.Value.ToString().Trim(), parentArray, currentArrayToken, input)); } } @@ -206,34 +192,34 @@ private void ParseToken(JToken parentToken, IDictionary parentAr else if (childToken.Type == JTokenType.String && childToken.Value().Trim().StartsWith("#") && parentArray != null && currentArrayToken != null) { - object newValue = ParseFunction(childToken.Value(), parentArray, currentArrayToken); + object newValue = ParseFunction(childToken.Value(), parentArray, currentArrayToken, input); childToken.Replace(GetToken(newValue)); } - if (!isLoop && !isBulk) + if (!helper.isLoop && !helper.isBulk) { - RecursiveEvaluate(ref childToken, parentArray, currentArrayToken); + RecursiveEvaluate(ref childToken, parentArray, currentArrayToken, input); } } - private void ParsePropertyFunction(IDictionary parentArray, IDictionary currentArrayToken, ref List loopProperties, ref List condProps, ref JArray arrayToForm, ref JObject dictToForm, ref List tokenToForm, ref List tokensToAdd, ref bool isLoop, JToken childToken, JProperty property, string functionName, string arguments) + private void ParsePropertyFunction(IDictionary parentArray, IDictionary currentArrayToken, TransformHelper helper, JToken childToken, JProperty property, string functionName, string arguments, JToken input) { switch (functionName) { case "ifgroup": - ConditionalGroupOperation(property.Name, arguments, parentArray, currentArrayToken, ref condProps, ref tokenToForm, childToken); + ConditionalGroupOperation(property.Name, arguments, parentArray, currentArrayToken, helper, childToken, input); break; case "loop": - LoopOperation(property.Name, arguments, parentArray, currentArrayToken, ref loopProperties, ref arrayToForm, ref dictToForm, childToken); - isLoop = true; + LoopOperation(property.Name, arguments, parentArray, currentArrayToken, helper, childToken, input); + helper.isLoop = true; break; case "eval": - EvalOperation(property, arguments, parentArray, currentArrayToken, ref loopProperties, ref tokensToAdd); + EvalOperation(property, arguments, parentArray, currentArrayToken, helper, input); break; } } - private void PostOperationsBuildUp(ref JToken parentToken, List tokenToForm) + private void PostOperationsBuildUp(ref JToken parentToken, IList tokenToForm) { if (tokenToForm != null) { @@ -265,7 +251,7 @@ private void PostOperationsBuildUp(ref JToken parentToken, List tokenToF } } - private void CopyPostOperationBuildUp(JToken parentToken, List selectedTokens) + private void CopyPostOperationBuildUp(JToken parentToken, IList selectedTokens) { foreach (JToken selectedToken in selectedTokens) { @@ -324,7 +310,7 @@ private static void CopyDescendants(JObject parent, JEnumerable children } } - private static void AddPostOperationBuildUp(JToken parentToken, List tokensToAdd) + private static void AddPostOperationBuildUp(JToken parentToken, IList tokensToAdd) { if (tokensToAdd != null) { @@ -335,7 +321,7 @@ private static void AddPostOperationBuildUp(JToken parentToken, List tok } } - private void DeletePostOperationBuildUp(JToken parentToken, List tokensToDelete) + private void DeletePostOperationBuildUp(JToken parentToken, IList tokensToDelete) { foreach (string selectedToken in tokensToDelete) @@ -348,7 +334,7 @@ private void DeletePostOperationBuildUp(JToken parentToken, List tokensT } - private static void ReplacePostOperationBuildUp(JToken parentToken, Dictionary tokensToReplace) + private static void ReplacePostOperationBuildUp(JToken parentToken, IDictionary tokensToReplace) { foreach (KeyValuePair tokenToReplace in tokensToReplace) @@ -359,15 +345,15 @@ private static void ReplacePostOperationBuildUp(JToken parentToken, Dictionary condProps, List loopProperties, JArray arrayToForm, JObject dictToForm) + private static void LoopPostOperationBuildUp(ref JToken parentToken, TransformHelper helper) { - if (loopProperties != null) + if (helper.loopProperties != null) { if (parentToken is JObject obj) { - foreach (string propertyToDelete in loopProperties) + foreach (string propertyToDelete in helper.loopProperties) { - if (dictToForm == null && arrayToForm == null && parentToken.Count() <= 1) + if (helper.dictToForm == null && helper.arrayToForm == null && parentToken.Count() <= 1) { obj.Replace(JValue.CreateNull()); } @@ -379,28 +365,28 @@ private static void LoopPostOperationBuildUp(ref JToken parentToken, List parentArray, IDictionary currentArrayToken, ref List loopProperties, ref JArray arrayToForm, ref JObject dictToForm, JToken childToken) + private void LoopOperation(string propertyName, string arguments, IDictionary parentArray, IDictionary currentArrayToken, TransformHelper helper, JToken childToken, JToken input) { var args = ExpressionHelper.SplitArguments(arguments, Context.EscapeChar); var previousAlias = "root"; - args[0] = (string)ParseFunction(args[0], parentArray, currentArrayToken); - string alias = args.Length > 1 ? (string)ParseFunction(args[1].Trim(), parentArray, currentArrayToken) : $"loop{++_loopCounter}"; + args[0] = (string)ParseFunction(args[0], parentArray, currentArrayToken, input); + string alias = args.Length > 1 ? (string)ParseFunction(args[1].Trim(), parentArray, currentArrayToken, input) : $"loop{++_loopCounter}"; if (args.Length > 2) { - previousAlias = (string)ParseFunction(args[2].Trim(), parentArray, currentArrayToken); - currentArrayToken = new Dictionary { { previousAlias, Context.Input } }; + previousAlias = (string)ParseFunction(args[2].Trim(), parentArray, currentArrayToken, input); + currentArrayToken = new Dictionary { { previousAlias, input } }; } else if (currentArrayToken?.Any() ?? false) { @@ -441,10 +427,10 @@ private void LoopOperation(string propertyName, string arguments, IDictionary { { previousAlias, Context.Input } }; + currentArrayToken = new Dictionary { { previousAlias, input } }; } - var strArrayToken = ParseArgument(parentArray, currentArrayToken, args[0]) as string; + var strArrayToken = ParseArgument(parentArray, currentArrayToken, args[0], input) as string; bool isDictionary = false; JToken arrayToken; @@ -484,9 +470,9 @@ private void LoopOperation(string propertyName, string arguments, IDictionary { { alias, array } }; } - if (arrayToForm == null) + if (helper.arrayToForm == null) { - arrayToForm = new JArray(); + helper.arrayToForm = new JArray(); } if (!isDictionary) { @@ -501,16 +487,16 @@ private void LoopOperation(string propertyName, string arguments, IDictionary t.First)) { - dictToForm.Add(replacedProperty); + helper.dictToForm.Add(replacedProperty); } } } @@ -536,20 +522,20 @@ private void LoopOperation(string propertyName, string arguments, IDictionary(); + if (helper.loopProperties == null) + helper.loopProperties = new List(); - loopProperties.Add(propertyName); + helper.loopProperties.Add(propertyName); _loopCounter--; } - private void ConditionalGroupOperation(string propertyName, string arguments, IDictionary parentArray, IDictionary currentArrayToken, ref List condProps, ref List tokenToForm, JToken childToken) + private void ConditionalGroupOperation(string propertyName, string arguments, IDictionary parentArray, IDictionary currentArrayToken, TransformHelper helper, JToken childToken, JToken input) { - object functionResult = ParseFunction(arguments, parentArray, currentArrayToken); + object functionResult = ParseFunction(arguments, parentArray, currentArrayToken, input); bool result; try { - result = (bool)ReflectionHelper.GetTypedValue(typeof(bool), functionResult, Context.EvaluationMode); + result = (bool)ReflectionHelper.GetTypedValue(typeof(bool), functionResult, Context.IsStrictMode()); } catch { @@ -559,65 +545,65 @@ private void ConditionalGroupOperation(string propertyName, string arguments, ID if (result) { - if (condProps == null) - condProps = new List(); + if (helper.condProps == null) + helper.condProps = new List(); - condProps.Add(propertyName); + helper.condProps.Add(propertyName); - RecursiveEvaluate(ref childToken, parentArray, currentArrayToken); + RecursiveEvaluate(ref childToken, parentArray, currentArrayToken, input); - if (tokenToForm == null) + if (helper.tokenToForm == null) { - tokenToForm = new List(); + helper.tokenToForm = new List(); } foreach (JToken grandChildToken in childToken.Children()) { - tokenToForm.Add(grandChildToken.DeepClone()); + helper.tokenToForm.Add(grandChildToken.DeepClone()); } } else { - if (condProps == null) + if (helper.condProps == null) { - condProps = new List(); + helper.condProps = new List(); } - condProps.Add(propertyName); + helper.condProps.Add(propertyName); } } - private void EvalOperation(JProperty property, string arguments, IDictionary parentArray, IDictionary currentArrayToken, ref List loopProperties, ref List tokensToAdd) + private void EvalOperation(JProperty property, string arguments, IDictionary parentArray, IDictionary currentArrayToken, TransformHelper helper, JToken input) { - object functionResult = ParseFunction(arguments, parentArray, currentArrayToken); + object functionResult = ParseFunction(arguments, parentArray, currentArrayToken, input); object val; if (property.Value.Type == JTokenType.String) { - val = ParseFunction(property.Value.Value(), parentArray, currentArrayToken); + val = ParseFunction(property.Value.Value(), parentArray, currentArrayToken, input); } else { var propVal = property.Value; - RecursiveEvaluate(ref propVal, parentArray, currentArrayToken); + RecursiveEvaluate(ref propVal, parentArray, currentArrayToken, input); val = property.Value; } JProperty clonedProperty = new JProperty(functionResult.ToString(), val); - if (loopProperties == null) - loopProperties = new List(); + if (helper.loopProperties == null) + helper.loopProperties = new List(); - loopProperties.Add(property.Name); + helper.loopProperties.Add(property.Name); - if (tokensToAdd == null) + if (helper.tokensToAdd == null) { - tokensToAdd = new List(); + helper.tokensToAdd = new List(); } - tokensToAdd.Add(clonedProperty); + helper.tokensToAdd.Add(clonedProperty); } - private void BulkOperations(JEnumerable arrayValues, IDictionary parentArray, IDictionary currentArrayToken, ref List selectedTokens, ref Dictionary tokensToReplace, ref List tokensToDelete) + private void BulkOperations(JEnumerable arrayValues, IDictionary parentArray, IDictionary currentArrayToken, TransformHelper helper, JToken input) { foreach (JToken arrayValue in arrayValues) { @@ -627,24 +613,24 @@ private void BulkOperations(JEnumerable arrayValues, IDictionary(); - selectedTokens.Add(Copy(arguments, parentArray, currentArrayToken)); + if (helper.selectedTokens == null) + helper.selectedTokens = new List(); + helper.selectedTokens.Add(Copy(arguments, parentArray, currentArrayToken, input)); } else if (functionName == "replace") { - if (tokensToReplace == null) - tokensToReplace = new Dictionary(); + if (helper.tokensToReplace == null) + helper.tokensToReplace = new Dictionary(); - var replaceResult = Replace(arguments, parentArray, currentArrayToken); - tokensToReplace.Add(replaceResult.Key, replaceResult.Value); + var replaceResult = Replace(arguments, parentArray, currentArrayToken, input); + helper.tokensToReplace.Add(replaceResult.Key, replaceResult.Value); } else if (functionName == "delete") { - if (tokensToDelete == null) - tokensToDelete = new List(); + if (helper.tokensToDelete == null) + helper.tokensToDelete = new List(); - tokensToDelete.Add(Delete(arguments, parentArray, currentArrayToken)); + helper.tokensToDelete.Add(Delete(arguments, parentArray, currentArrayToken, input)); } } } @@ -717,7 +703,7 @@ private JToken GetToken(object newValue) return result; } - private IEnumerable TransformArray(JEnumerable children, IDictionary parentArray, IDictionary currentArrayToken) + private IEnumerable TransformArray(JEnumerable children, IDictionary parentArray, IDictionary currentArrayToken, JToken input) { var result = new List(); @@ -726,7 +712,7 @@ private IEnumerable TransformArray(JEnumerable children, IDictio object itemToAdd = arrEl.Value(); if (arrEl.Type == JTokenType.String && arrEl.ToString().Trim().StartsWith("#")) { - itemToAdd = ParseFunction(arrEl.ToString(), parentArray, currentArrayToken); + itemToAdd = ParseFunction(arrEl.ToString(), parentArray, currentArrayToken, input); } result.Add(itemToAdd); } @@ -735,11 +721,11 @@ private IEnumerable TransformArray(JEnumerable children, IDictio } #region Copy - private JToken Copy(string arguments, IDictionary parentArray, IDictionary currentArrayElement) + private JToken Copy(string arguments, IDictionary parentArray, IDictionary currentArrayElement, JToken input) { string[] argumentArr = ExpressionHelper.SplitArguments(arguments, Context.EscapeChar); string path = argumentArr[0]; - if (!(ParseArgument(parentArray, currentArrayElement, path) is string jsonPath)) + if (!(ParseArgument(parentArray, currentArrayElement, path, input) is string jsonPath)) { throw new ArgumentException($"Invalid path for #copy: '{argumentArr[0]}' resolved to null!"); } @@ -747,32 +733,32 @@ private JToken Copy(string arguments, IDictionary parentArray, I string alias = null; if (argumentArr.Length > 1) { - alias = ParseArgument(parentArray, currentArrayElement, argumentArr[1]) as string; + alias = ParseArgument(parentArray, currentArrayElement, argumentArr[1], input) as string; if (!(currentArrayElement?.ContainsKey(alias) ?? false)) { throw new ArgumentException($"Unknown loop alias: '{argumentArr[1]}'"); } } - JToken input = alias != null ? currentArrayElement[alias] : currentArrayElement?.Last().Value ?? Context.Input; - JToken selectedToken = GetSelectableToken(input, Context).Select(jsonPath); + JToken localInput = alias != null ? currentArrayElement[alias] : currentArrayElement?.Last().Value ?? input; + JToken selectedToken = GetSelectableToken(localInput, Context).Select(jsonPath); return selectedToken; } #endregion #region Replace - private KeyValuePair Replace(string arguments, IDictionary parentArray, IDictionary currentArrayElement) + private KeyValuePair Replace(string arguments, IDictionary parentArray, IDictionary currentArrayElement, JToken input) { string[] argumentArr = ExpressionHelper.SplitArguments(arguments, Context.EscapeChar); if (argumentArr.Length < 2) { throw new Exception("Function #replace needs at least two arguments - 1. path to be replaced, 2. token to replace with."); } - if (!(ParseArgument(parentArray, currentArrayElement, argumentArr[0]) is string key)) + if (!(ParseArgument(parentArray, currentArrayElement, argumentArr[0], input) is string key)) { throw new ArgumentException($"Invalid path for #replace: '{argumentArr[0]}' resolved to null!"); } - object str = ParseArgument(parentArray, currentArrayElement, argumentArr[1]); + object str = ParseArgument(parentArray, currentArrayElement, argumentArr[1], input); JToken newToken = GetToken(str); return new KeyValuePair(key, newToken); } @@ -780,9 +766,9 @@ private KeyValuePair Replace(string arguments, IDictionary parentArray, IDictionary currentArrayElement) + private string Delete(string argument, IDictionary parentArray, IDictionary currentArrayElement, JToken input) { - if (!(ParseArgument(parentArray, currentArrayElement, argument) is string result)) + if (!(ParseArgument(parentArray, currentArrayElement, argument, input) is string result)) { throw new ArgumentException($"Invalid path for #delete: '{argument}' resolved to null!"); } @@ -792,7 +778,7 @@ private string Delete(string argument, IDictionary parentArray, #region ParseFunction - private object ParseFunction(string functionString, IDictionary array, IDictionary currentArrayElement) + private object ParseFunction(string functionString, IDictionary array, IDictionary currentArrayElement, JToken input) { try { @@ -807,23 +793,16 @@ private object ParseFunction(string functionString, IDictionary if (functionName == "ifcondition") { - var condition = ParseArgument(array, currentArrayElement, arguments[0]); - var value = ParseArgument(array, currentArrayElement, arguments[1]); - var equal = ComparisonHelper.Equals(condition, value, Context.EvaluationMode); - var index = (equal) ? 2 : 3; - - output = ParseArgument(array, currentArrayElement, arguments[index]); + output = ConditionalFunction(array, currentArrayElement, arguments, input); } else { int i = 0; for (; i < (arguments?.Length ?? 0); i++) { - listParameters.Add(ParseArgument(array, currentArrayElement, arguments[i])); + listParameters.Add(ParseArgument(array, currentArrayElement, arguments[i], input)); } - listParameters.Add(Context); - - var parameters = listParameters.ToArray(); + var convertParameters = true; if (new[] { "concat", "xconcat", "currentproperty" }.Contains(functionName)) { @@ -833,7 +812,13 @@ private object ParseFunction(string functionString, IDictionary if (new[] { "currentvalue", "currentindex", "lastindex", "lastvalue" }.Contains(functionName)) { var alias = ParseLoopAlias(listParameters, 1, array.Last().Key); - output = ReflectionHelper.Caller(null, "JUST.Transformer`1", functionName, new object[] { array[alias], currentArrayElement[alias] }, convertParameters, Context); + output = ReflectionHelper.Caller( + null, + "JUST.Transformer`1", + functionName, + new object[] { array[alias], currentArrayElement[alias] }, + convertParameters, + Context); } else if (new[] { "currentvalueatpath", "lastvalueatpath" }.Contains(functionName)) { @@ -842,7 +827,7 @@ private object ParseFunction(string functionString, IDictionary null, "JUST.Transformer`1", functionName, - new[] { array[alias], currentArrayElement[alias] }.Concat(listParameters.ToArray()).ToArray(), + new[] { array[alias], currentArrayElement[alias] }.Concat(new object[] { listParameters[0], Context }).ToArray(), convertParameters, Context); } @@ -854,11 +839,12 @@ private object ParseFunction(string functionString, IDictionary convertParameters, Context); } else if (functionName == "customfunction") - output = CallCustomFunction(listParameters.ToArray()); + output = CallCustomFunction(listParameters.Concat(new object[] { currentArrayElement?.Last().Value ?? + input, Context }).ToArray()); else if (Context?.IsRegisteredCustomFunction(functionName) ?? false) { var methodInfo = Context.GetCustomMethod(functionName); - output = ReflectionHelper.InvokeCustomMethod(methodInfo, parameters, convertParameters, Context); + output = ReflectionHelper.InvokeCustomMethod(methodInfo, listParameters.ToArray(), convertParameters, Context); } else if (Regex.IsMatch(functionName, ReflectionHelper.EXTERNAL_ASSEMBLY_REGEX)) { @@ -869,22 +855,26 @@ private object ParseFunction(string functionString, IDictionary "stringcontains", "stringequals"}.Contains(functionName)) { object[] oParams = new object[1]; - oParams[0] = parameters; + oParams[0] = listParameters.Concat(new object[] { Context }).ToArray(); output = ReflectionHelper.Caller(null, "JUST.Transformer`1", functionName, oParams, convertParameters, Context); } else if (functionName == "applyover") { - output = ParseApplyOver(array, currentArrayElement, parameters); + output = ParseApplyOver(array, currentArrayElement, listParameters.Concat(new object[] { Context }).ToArray(), input); } else { - var input = ((JUSTContext)listParameters.Last()).Input; - if (currentArrayElement != null && functionName != "valueof") - { - ((JUSTContext)listParameters.Last()).Input = currentArrayElement.Last().Value; - } - output = ReflectionHelper.Caller(null, "JUST.Transformer`1", functionName, parameters, convertParameters, Context); - ((JUSTContext)parameters.Last()).Input = input; + var inputToken = currentArrayElement != null && functionName != "valueof" ? + currentArrayElement.Last().Value : + input; + + output = ReflectionHelper.Caller( + null, + "JUST.Transformer`1", + functionName, + listParameters.Concat(new object[] { inputToken, Context }).ToArray(), + convertParameters, + Context); } } @@ -896,34 +886,41 @@ private object ParseFunction(string functionString, IDictionary } } - private object ParseApplyOver(IDictionary array, IDictionary currentArrayElement, object[] parameters) + private object ConditionalFunction(IDictionary array, IDictionary currentArrayElement, string[] arguments, JToken input) + { + var condition = ParseArgument(array, currentArrayElement, arguments[0], input); + var value = ParseArgument(array, currentArrayElement, arguments[1], input); + var equal = ComparisonHelper.Equals(condition, value, Context.EvaluationMode); + var index = (equal) ? 2 : 3; + + return ParseArgument(array, currentArrayElement, arguments[index], input); + } + + private object ParseApplyOver(IDictionary array, IDictionary currentArrayElement, object[] parameters, JToken input) { object output; - var contextInput = Context.Input; - var input = JToken.Parse(Transform(parameters[0].ToString(), contextInput.ToString())); - Context.Input = input; - if (parameters[1].ToString().Trim().Trim('\'').StartsWith('{')) + string localInput = Transform(parameters[0].ToString(), input.ToString()); + if (parameters[1].ToString().Trim().Trim('\'').StartsWith("{")) { var jobj = JObject.Parse(parameters[1].ToString().Trim().Trim('\'')); - output = new JsonTransformer(Context).Transform(jobj, input); + output = new JsonTransformer(Context).Transform(jobj, localInput); } - else if (parameters[1].ToString().Trim().Trim('\'').StartsWith('[')) + else if (parameters[1].ToString().Trim().Trim('\'').StartsWith("[")) { var jarr = JArray.Parse(parameters[1].ToString().Trim().Trim('\'')); - output = new JsonTransformer(Context).Transform(jarr, input); + output = new JsonTransformer(Context).Transform(jarr, localInput); } else { - output = ParseFunction(parameters[1].ToString().Trim().Trim('\''), array, currentArrayElement); + output = ParseFunction(parameters[1].ToString().Trim().Trim('\''), array, currentArrayElement, JToken.Parse(localInput)); } - Context.Input = contextInput; return output; } private string ParseLoopAlias(List listParameters, int index, string defaultValue) { string alias; - if (listParameters.Count > index) + if (listParameters.Count >= index) { alias = (listParameters[index - 1] as string).Trim(); listParameters.RemoveAt(index - 1); @@ -935,12 +932,12 @@ private string ParseLoopAlias(List listParameters, int index, string def return alias; } - private object ParseArgument(IDictionary array, IDictionary currentArrayElement, string argument) + private object ParseArgument(IDictionary array, IDictionary currentArrayElement, string argument, JToken input) { var trimmedArgument = argument.Trim(); if (trimmedArgument.StartsWith("#")) { - return ParseFunction(trimmedArgument, array, currentArrayElement); + return ParseFunction(trimmedArgument, array, currentArrayElement, input); } if (trimmedArgument.StartsWith($"{Context.EscapeChar}#")) { diff --git a/JUST.net/ReflectionHelper.cs b/JUST.net/ReflectionHelper.cs index 9e92126..6a44f42 100644 --- a/JUST.net/ReflectionHelper.cs +++ b/JUST.net/ReflectionHelper.cs @@ -15,7 +15,7 @@ internal static class ReflectionHelper { internal const string EXTERNAL_ASSEMBLY_REGEX = "([\\w.]+)[:]{2}([\\w.]+)[:]{0,2}([\\w.]*)"; - internal static object Caller(Assembly assembly, string myclass, string mymethod, object[] parameters, bool convertParameters, JUSTContext context) where T : ISelectableToken + internal static object Caller(Assembly assembly, string myclass, string mymethod, object[] parameters, bool convertParameters, IContext context) where T : ISelectableToken { Type type = assembly?.GetType(myclass) ?? Type.GetType(myclass); if (type?.ContainsGenericParameters ?? false) @@ -33,12 +33,12 @@ internal static object Caller(Assembly assembly, string myclass, string mymet } catch (Exception ex) { - ExceptionHelper.HandleException(ex, context.EvaluationMode); + ExceptionHelper.HandleException(ex, context.IsStrictMode()); } return GetDefaultValue(methodInfo.ReturnType); } - internal static object InvokeCustomMethod(MethodInfo methodInfo, object[] parameters, bool convertParameters, JUSTContext context) where T : ISelectableToken + internal static object InvokeCustomMethod(MethodInfo methodInfo, object[] parameters, bool convertParameters, IContext context) where T : ISelectableToken { var instance = !methodInfo.IsStatic ? Activator.CreateInstance(methodInfo.DeclaringType) : null; @@ -49,7 +49,7 @@ internal static object InvokeCustomMethod(MethodInfo methodInfo, object[] par for (int i = 0; i < parameterInfos.Length; i++) { var pType = parameterInfos[i].ParameterType; - typedParameters.Add(GetTypedValue(pType, parameters[i], context.EvaluationMode)); + typedParameters.Add(GetTypedValue(pType, parameters[i], context.IsStrictMode())); } } try @@ -198,12 +198,12 @@ internal static Type GetType(JTokenType jType) return result; } - internal static object GetTypedValue(JTokenType jType, object val, EvaluationMode mode) + internal static object GetTypedValue(JTokenType jType, object val, bool IsStrictMode) { - return GetTypedValue(GetType(jType), val, mode); + return GetTypedValue(GetType(jType), val, IsStrictMode); } - internal static object GetTypedValue(Type pType, object val, EvaluationMode mode) + internal static object GetTypedValue(Type pType, object val, bool IsStrictMode) { object typedValue = val; var converter = TypeDescriptor.GetConverter(pType); @@ -251,7 +251,7 @@ internal static object GetTypedValue(Type pType, object val, EvaluationMode mode } catch (Exception ex) { - ExceptionHelper.HandleException(ex, mode); + ExceptionHelper.HandleException(ex, IsStrictMode); typedValue = GetDefaultValue(pType); } return typedValue; diff --git a/JUST.net/TransformHelper.cs b/JUST.net/TransformHelper.cs new file mode 100644 index 0000000..2609bf2 --- /dev/null +++ b/JUST.net/TransformHelper.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +internal class TransformHelper +{ + internal IList selectedTokens; + internal IDictionary tokensToReplace; + internal IList tokensToDelete; + internal IList loopProperties; + internal IList condProps; + internal JArray arrayToForm; + internal JObject dictToForm; + internal IList tokenToForm; + internal IList tokensToAdd; + internal bool isLoop; + internal bool isBulk; +} \ No newline at end of file diff --git a/JUST.net/Transformer.cs b/JUST.net/Transformer.cs index 7d12a5b..15202aa 100644 --- a/JUST.net/Transformer.cs +++ b/JUST.net/Transformer.cs @@ -22,6 +22,13 @@ protected static object TypedNumber(decimal number) return number * 10 % 10 == 0 ? (number <= int.MaxValue ? (object)Convert.ToInt32(number) : number) : number; } + + internal static JToken GetToken(JToken input, string path, IContext context) where T : ISelectableToken + { + T selector = context.Resolve(input); + return selector.Select(path); + } + internal static object GetValue(JToken selectedToken) { object output = null; @@ -84,31 +91,28 @@ public Transformer(JUSTContext context) : base(context) { } - public static object valueof(string path, JUSTContext context) + public static object valueof(string path, JToken input, IContext context) { - var selector = context.Resolve(context.Input); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(input, path, context); return GetValue(selectedToken); } - public static bool exists(string path, JUSTContext context) + public static bool exists(string path, JToken input, IContext context) { - var selector = context.Resolve(context.Input); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(input, path, context); return selectedToken != null; } - public static bool existsandnotempty(string path, JUSTContext context) + public static bool existsandnotempty(string path, JToken input, IContext context) { - var selector = context.Resolve(context.Input); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(input, path, context); return selectedToken != null && ( (selectedToken.Type == JTokenType.String && selectedToken.ToString().Trim() != string.Empty) || (selectedToken.Type == JTokenType.Array && selectedToken.Children().Count() > 0) ); } - public static object ifcondition(object condition, object value, object trueResult, object falseResult, JUSTContext context) + public static object ifcondition(object condition, object value, object trueResult, object falseResult, JToken input, IContext context) { object output = falseResult; @@ -152,7 +156,7 @@ private static object ConcatArray(object obj1, object obj2) return item.ToObject(); } - public static object concat(object obj1, object obj2, JUSTContext context) + public static object concat(object obj1, object obj2, JToken input, IContext context) { if (obj1 != null) { @@ -174,7 +178,7 @@ public static object concat(object obj1, object obj2, JUSTContext context) return null; } - public static string substring(string stringRef, int startIndex, int length, JUSTContext context) + public static string substring(string stringRef, int startIndex, int length, JToken input, IContext context) { try { @@ -182,27 +186,27 @@ public static string substring(string stringRef, int startIndex, int length, JUS } catch (Exception ex) { - ExceptionHelper.HandleException(ex, context.EvaluationMode); + ExceptionHelper.HandleException(ex, context.IsStrictMode()); } return null; } - public static int firstindexof(string stringRef, string searchString, JUSTContext context) + public static int firstindexof(string stringRef, string searchString, JToken input, IContext context) { return stringRef.IndexOf(searchString, 0); } - public static int lastindexof(string stringRef, string searchString, JUSTContext context) + public static int lastindexof(string stringRef, string searchString, JToken input, IContext context) { return stringRef.LastIndexOf(searchString); } - public static string concatall(object obj, JUSTContext context) + public static string concatall(object obj, JToken input, IContext context) { JToken token = JToken.FromObject(obj); if (obj is string path && path.StartsWith(context.Resolve(token).RootReference)) { - return Concatall(JToken.FromObject(valueof(path, context)), context); + return Concatall(JToken.FromObject(valueof(path, input, context)), context); } else { @@ -210,7 +214,7 @@ public static string concatall(object obj, JUSTContext context) } } - private static string Concatall(JToken parsedArray, JUSTContext context) + private static string Concatall(JToken parsedArray, IContext context) { string result = null; @@ -233,7 +237,7 @@ private static string Concatall(JToken parsedArray, JUSTContext context) return result; } - public static string concatallatpath(JArray parsedArray, string path, JUSTContext context) + public static string concatallatpath(JArray parsedArray, string path, JToken input, IContext context) { string result = null; @@ -242,8 +246,7 @@ public static string concatallatpath(JArray parsedArray, string path, JUSTContex result = string.Empty; foreach (JToken token in parsedArray.Children()) { - var selector = context.Resolve(token); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(token, path, context); if (context.IsStrictMode() && selectedToken.Type != JTokenType.String) { throw new Exception($"Invalid value in array to concatenate: {selectedToken.ToString()}"); @@ -260,40 +263,40 @@ public static string concatallatpath(JArray parsedArray, string path, JUSTContex #region math functions - public static object add(decimal num1, decimal num2, JUSTContext context) + public static object add(decimal num1, decimal num2, JToken input, IContext context) { return TypedNumber(num1 + num2); } - public static object subtract(decimal num1, decimal num2, JUSTContext context) + public static object subtract(decimal num1, decimal num2, JToken input, IContext context) { return TypedNumber(num1 - num2); } - public static object multiply(decimal num1, decimal num2, JUSTContext context) + public static object multiply(decimal num1, decimal num2, JToken input, IContext context) { return TypedNumber(num1 * num2); } - public static object divide(decimal num1, decimal num2, JUSTContext context) + public static object divide(decimal num1, decimal num2, JToken input, IContext context) { return TypedNumber(num1 / num2); } #endregion #region aggregate functions - public static object sum(object obj, JUSTContext context) + public static object sum(object obj, JToken input, IContext context) { JToken token = JToken.FromObject(obj); if (obj is string path && path.StartsWith(context.Resolve(token).RootReference)) { - return Sum(JToken.FromObject(valueof(path, context)), context); + return Sum(JToken.FromObject(valueof(path, input, context))); } else { - return Sum(token, context); + return Sum(token); } } - private static object Sum(JToken parsedArray, JUSTContext context) + private static object Sum(JToken parsedArray) { decimal result = 0; if (parsedArray != null) @@ -307,15 +310,14 @@ private static object Sum(JToken parsedArray, JUSTContext context) return TypedNumber(result); } - public static object sumatpath(JArray parsedArray, string path, JUSTContext context) + public static object sumatpath(JArray parsedArray, string path, JToken input, IContext context) { decimal result = 0; if (parsedArray != null) { foreach (JToken token in parsedArray.Children()) { - var selector = context.Resolve(token); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(token, path, context); result += Convert.ToDecimal(selectedToken.ToString()); } } @@ -323,20 +325,20 @@ public static object sumatpath(JArray parsedArray, string path, JUSTContext cont return TypedNumber(result); } - public static object average(object obj, JUSTContext context) + public static object average(object obj, JToken input, IContext context) { JToken token = JToken.FromObject(obj); if (obj is string path && path.StartsWith(context.Resolve(token).RootReference)) { - return Average(JToken.FromObject(valueof(path, context)), context); + return Average(JToken.FromObject(valueof(path, input, context))); } else { - return Average(token, context); + return Average(token); } } - private static object Average(JToken token, JUSTContext context) + private static object Average(JToken token) { decimal result = 0; JArray parsedArray = token as JArray; @@ -351,7 +353,7 @@ private static object Average(JToken token, JUSTContext context) return TypedNumber(result / parsedArray.Count); } - public static object averageatpath(JArray parsedArray, string path, JUSTContext context) + public static object averageatpath(JArray parsedArray, string path, JToken input, IContext context) { decimal result = 0; @@ -359,8 +361,7 @@ public static object averageatpath(JArray parsedArray, string path, JUSTContext { foreach (JToken token in parsedArray.Children()) { - var selector = context.Resolve(token); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(token, path, context); result += Convert.ToDecimal(selectedToken.ToString()); } } @@ -368,20 +369,20 @@ public static object averageatpath(JArray parsedArray, string path, JUSTContext return TypedNumber(result / parsedArray.Count); } - public static object max(object obj, JUSTContext context) + public static object max(object obj, JToken input, IContext context) { JToken token = JToken.FromObject(obj); if (obj is string path && path.StartsWith(context.Resolve(token).RootReference)) { - return Max(JToken.FromObject(valueof(path, context)), context); + return Max(JToken.FromObject(valueof(path, input, context))); } else { - return Max(token, context); + return Max(token); } } - private static object Max(JToken token, JUSTContext context) + private static object Max(JToken token) { decimal result = 0; if (token != null) @@ -401,15 +402,14 @@ private static decimal Max(decimal d1, JToken token) return Math.Max(d1, thisValue); } - public static object maxatpath(JArray parsedArray, string path, JUSTContext context) + public static object maxatpath(JArray parsedArray, string path, JToken input, IContext context) { decimal result = 0; if (parsedArray != null) { foreach (JToken token in parsedArray.Children()) { - var selector = context.Resolve(token); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(token, path, context); result = Max(result, selectedToken); } } @@ -417,20 +417,20 @@ public static object maxatpath(JArray parsedArray, string path, JUSTContext cont return TypedNumber(result); } - public static object min(object obj, JUSTContext context) + public static object min(object obj, JToken input, IContext context) { JToken token = JToken.FromObject(obj); if (obj is string path && path.StartsWith(context.Resolve(token).RootReference)) { - return Min(JToken.FromObject(valueof(path, context)), context); + return Min(JToken.FromObject(valueof(path, input, context))); } else { - return Min(token, context); + return Min(token); } } - private static object Min(JToken token, JUSTContext context) + private static object Min(JToken token) { decimal result = decimal.MaxValue; if (token != null) @@ -445,7 +445,7 @@ private static object Min(JToken token, JUSTContext context) return TypedNumber(result); } - public static object minatpath(JArray parsedArray, string path, JUSTContext context) + public static object minatpath(JArray parsedArray, string path, JToken input, IContext context) { decimal result = decimal.MaxValue; @@ -453,8 +453,7 @@ public static object minatpath(JArray parsedArray, string path, JUSTContext cont { foreach (JToken token in parsedArray.Children()) { - var selector = context.Resolve(token); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(token, path, context); decimal thisValue = Convert.ToDecimal(selectedToken.ToString()); result = Math.Min(result, thisValue); } @@ -463,7 +462,7 @@ public static object minatpath(JArray parsedArray, string path, JUSTContext cont return TypedNumber(result); } - public static int arraylength(string array, JUSTContext context) + public static int arraylength(string array, JToken input, IContext context) { JArray parsedArray = JArray.Parse(array); return parsedArray.Count; @@ -492,14 +491,13 @@ public static int lastindex(JArray array, JToken currentElement) return array.Count - 1; } - public static object currentvalueatpath(JArray array, JToken currentElement, string path, JUSTContext context) + public static object currentvalueatpath(JArray array, JToken currentElement, string path, IContext context) { - var selector = context.Resolve(currentElement); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(currentElement, path, context); return GetValue(selectedToken); } - public static object currentproperty(JArray array, JToken currentElement, JUSTContext context) + public static object currentproperty(JArray array, JToken currentElement, IContext context) { var prop = (currentElement.First as JProperty); if (prop == null && context.IsStrictMode()) @@ -509,22 +507,21 @@ public static object currentproperty(JArray array, JToken currentElement, JUSTCo return prop.Name; } - public static object lastvalueatpath(JArray array, JToken currentElement, string path, JUSTContext context) + public static object lastvalueatpath(JArray array, JToken currentElement, string path, IContext context) { - var selector = context.Resolve(array.Last); - JToken selectedToken = selector.Select(path); + JToken selectedToken = GetToken(array.Last(), path, context); return GetValue(selectedToken); } #endregion #region Constants - public static string constant_comma(JUSTContext context) + public static string constant_comma(JToken input, IContext context) { return ","; } - public static string constant_hash(JUSTContext context) + public static string constant_hash(JToken input, IContext context) { return "#"; } @@ -540,7 +537,7 @@ public static object xconcat(object[] list) { if (list[i] != null) { - result = concat(result, list[i], null); + result = concat(result, list[i], null, null); } } @@ -549,12 +546,12 @@ public static object xconcat(object[] list) public static object xadd(object[] list) { - JUSTContext context = list[list.Length - 1] as JUSTContext; + IContext context = list[list.Length - 1] as IContext; decimal add = 0; for (int i = 0; i < list.Length - 1; i++) { if (list[i] != null) - add += (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[i], context.EvaluationMode); + add += (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[i], context.IsStrictMode()); } return TypedNumber(add); @@ -562,11 +559,10 @@ public static object xadd(object[] list) #endregion #region grouparrayby - public static JArray grouparrayby(string path, string groupingElement, string groupedElement, JUSTContext context) + public static JArray grouparrayby(string path, string groupingElement, string groupedElement, JToken input, IContext context) { JArray result; - var selector = context.Resolve(context.Input); - JArray arr = (JArray)selector.Select(path); + JArray arr = (JArray)GetToken(input, path, context); if (!groupingElement.Contains(context.SplitGroupChar)) { result = Utilities.GroupArray(arr, groupingElement, groupedElement, context); @@ -623,10 +619,10 @@ public static bool mathequals(object[] list) { decimal lshDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[0], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); decimal rhsDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[1], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); result = lshDecimal == rhsDecimal; } @@ -641,10 +637,10 @@ public static bool mathgreaterthan(object[] list) { decimal lshDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[0], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); decimal rhsDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[1], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); result = lshDecimal > rhsDecimal; } @@ -659,10 +655,10 @@ public static bool mathlessthan(object[] list) { decimal lshDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[0], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); decimal rhsDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[1], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); result = lshDecimal < rhsDecimal; } @@ -677,10 +673,10 @@ public static bool mathgreaterthanorequalto(object[] list) { decimal lshDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[0], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); decimal rhsDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[1], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); result = lshDecimal >= rhsDecimal; } @@ -695,10 +691,10 @@ public static bool mathlessthanorequalto(object[] list) { decimal lshDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[0], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); decimal rhsDecimal = (decimal)ReflectionHelper.GetTypedValue(typeof(decimal), list[1], - list.Length >= 3 ? ((JUSTContext)list[2]).EvaluationMode : EvaluationMode.Strict); + list.Length >= 3 ? ((IContext)list[2]).IsStrictMode() : true); result = lshDecimal <= rhsDecimal; } @@ -707,37 +703,37 @@ public static bool mathlessthanorequalto(object[] list) } #endregion - public static object tointeger(object val, JUSTContext context) + public static object tointeger(object val, JToken input, IContext context) { - return ReflectionHelper.GetTypedValue(typeof(int), val, context.EvaluationMode); + return ReflectionHelper.GetTypedValue(typeof(int), val, context.IsStrictMode()); } - public static object tostring(object val, JUSTContext context) + public static object tostring(object val, JToken input, IContext context) { - return ReflectionHelper.GetTypedValue(typeof(string), val, context.EvaluationMode); + return ReflectionHelper.GetTypedValue(typeof(string), val, context.IsStrictMode()); } - public static object toboolean(object val, JUSTContext context) + public static object toboolean(object val, JToken input, IContext context) { - return ReflectionHelper.GetTypedValue(typeof(bool), val, context.EvaluationMode); + return ReflectionHelper.GetTypedValue(typeof(bool), val, context.IsStrictMode()); } - public static decimal todecimal(object val, JUSTContext context) + public static decimal todecimal(object val, JToken input, IContext context) { - return decimal.Round((decimal)ReflectionHelper.GetTypedValue(typeof(decimal), val, context.EvaluationMode), context.DefaultDecimalPlaces); + return decimal.Round((decimal)ReflectionHelper.GetTypedValue(typeof(decimal), val, context.IsStrictMode()), context.DefaultDecimalPlaces); } - public static decimal round(decimal val, int decimalPlaces, JUSTContext context) + public static decimal round(decimal val, int decimalPlaces, JToken input, IContext context) { return decimal.Round(val, decimalPlaces, MidpointRounding.AwayFromZero); } - public static int length(object val, JUSTContext context) + public static int length(object val, JToken input, IContext context) { int result = 0; if (val is string path && path.StartsWith(context.Resolve(null).RootReference)) { - result = length(valueof(path, context), context); + result = length(valueof(path, input, context), input, context); } else if (val is IEnumerable enumerable) { @@ -756,11 +752,11 @@ public static int length(object val, JUSTContext context) } return result; } - public static bool isnumber(object val, JUSTContext context) + public static bool isnumber(object val, JToken input, IContext context) { try { - object r = ReflectionHelper.GetTypedValue(typeof(decimal), val, context.EvaluationMode); + object r = ReflectionHelper.GetTypedValue(typeof(decimal), val, context.IsStrictMode()); return r is decimal; } catch @@ -769,17 +765,17 @@ public static bool isnumber(object val, JUSTContext context) } } - public static bool isboolean(object val, JUSTContext context) + public static bool isboolean(object val, JToken input, IContext context) { return val.GetType() == typeof(bool); } - public static bool isstring(object val, JUSTContext context) + public static bool isstring(object val, JToken input, IContext context) { return val.GetType() == typeof(string); } - public static bool isarray(object val, JUSTContext context) + public static bool isarray(object val, JToken input, IContext context) { return val.GetType().IsArray; } @@ -794,7 +790,7 @@ public static string stringempty() return string.Empty; } - public static object arrayempty(JUSTContext context) + public static object arrayempty(JToken input, IContext context) { return Array.Empty(); } diff --git a/JUST.net/Utilities.cs b/JUST.net/Utilities.cs index fb688e7..ee1aa8c 100644 --- a/JUST.net/Utilities.cs +++ b/JUST.net/Utilities.cs @@ -45,7 +45,7 @@ private static Dictionary PopulateRecursively(JToken parent, Dic return result; } - public static JArray GroupArray(JArray array, string groupingPropertyName, string groupedPropertyName, JUSTContext context) where T: ISelectableToken + public static JArray GroupArray(JArray array, string groupingPropertyName, string groupedPropertyName, IContext context) where T: ISelectableToken { Dictionary groupedPair = null; @@ -55,7 +55,6 @@ public static JArray GroupArray(JArray array, string groupingPropertyName, st { var selectable = context.Resolve(eachObj); JToken groupToken = selectable.Select(selectable.RootReference + groupingPropertyName); - if (groupedPair == null) groupedPair = new Dictionary(); @@ -105,7 +104,7 @@ public static JArray GroupArray(JArray array, string groupingPropertyName, st return resultObj; } - public static JArray GroupArrayMultipleProperties(JArray array, string[] groupingPropertyNames, string groupedPropertyName, JUSTContext context) where T: ISelectableToken + public static JArray GroupArrayMultipleProperties(JArray array, string[] groupingPropertyNames, string groupedPropertyName, IContext context) where T: ISelectableToken { Dictionary groupedPair = null; 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..b3fd239 100644 --- a/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj +++ b/UnitTestForExternalAssemblyBug/UnitTestForExternalAssemblyBug.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/UnitTests/Arrays/AggregateFunctionsTests.cs b/UnitTests/Arrays/AggregateFunctionsTests.cs index 292824c..0daa64a 100644 --- a/UnitTests/Arrays/AggregateFunctionsTests.cs +++ b/UnitTests/Arrays/AggregateFunctionsTests.cs @@ -113,7 +113,7 @@ public void AverageAtPath() { const string transformer = "{ \"avg_at_path\": \"#averageatpath(#valueof($.x),$.v.c)\" }"; - var result = new JsonTransformer().Transform(transformer, ExampleInputs.MultiDimensionalArray); + var result = new JsonTransformer(new JUSTContext { EvaluationMode = EvaluationMode.Strict }).Transform(transformer, ExampleInputs.MultiDimensionalArray); Assert.AreEqual("{\"avg_at_path\":20}", result); } diff --git a/UnitTests/Arrays/LoopingTests.cs b/UnitTests/Arrays/LoopingTests.cs index e351766..28347f2 100644 --- a/UnitTests/Arrays/LoopingTests.cs +++ b/UnitTests/Arrays/LoopingTests.cs @@ -140,7 +140,7 @@ public void NestedLoopingContextInput() { const string transformer = "{ \"hello\": { \"#loop($.NestedLoop.Organization.Employee)\": { \"Details\": { \"#loop($.Details)\": { \"Exists\": \"#exists($.Country)\", \"IsIsland\": \"#ifcondition(#currentvalueatpath($.Country),Iceland,#toboolean(True),#toboolean(False))\", \"CurrentCountry\": \"#currentvalueatpath($.Country)\" } } } } }"; - var result = new JsonTransformer().Transform(transformer, ExampleInputs.NestedArrays); + var result = new JsonTransformer(new JUSTContext { EvaluationMode = EvaluationMode.Strict }).Transform(transformer, ExampleInputs.NestedArrays); Assert.AreEqual("{\"hello\":[{\"Details\":[{\"Exists\":true,\"IsIsland\":true,\"CurrentCountry\":\"Iceland\"}]},{\"Details\":[{\"Exists\":true,\"IsIsland\":false,\"CurrentCountry\":\"Denmark\"}]}]}", result); } diff --git a/UnitTests/CustomFunctionsTest.cs b/UnitTests/CustomFunctionsTest.cs index a98a2ab..a953bf4 100644 --- a/UnitTests/CustomFunctionsTest.cs +++ b/UnitTests/CustomFunctionsTest.cs @@ -14,7 +14,7 @@ public void XmlTest() string inputSpecial = File.ReadAllText("Inputs/Input_Customfunction_Nestedresult.json"); string transformer = File.ReadAllText("Inputs/Transformer_customfunctionnestedresult.json"); string result = JsonConvert.SerializeObject - (new JsonTransformer().Transform(JObject.Parse(transformer), JObject.Parse(inputSpecial))); + (new JsonTransformer(new JUSTContext { EvaluationMode = EvaluationMode.Strict }).Transform(JObject.Parse(transformer), JObject.Parse(inputSpecial))); Assert.AreEqual("{\"Seasons\":[[[[\"2017\"],[\"40\"],[\"20\"],[\"25\"],[\"10\"]],[[\"2018\"],[\"40\"],[\"20\"],[\"25\"],[\"10\"]],[[\"2019\"],[\"40\"],[\"20\"],[\"25\"],[\"10\"]]]]}", result); } diff --git a/UnitTests/Inputs/thread_safe_input.json b/UnitTests/Inputs/thread_safe_input.json new file mode 100644 index 0000000..00e3f4a --- /dev/null +++ b/UnitTests/Inputs/thread_safe_input.json @@ -0,0 +1,88 @@ +{ + "list": [ + { + "title": "Mr.", + "name": "Smith", + "addresses": [ + { + "street": "Some Street", + "number": 1, + "city": "Some City", + "postal_code": 1234 + }, + { + "street": "Some Other Street", + "number": 2, + "city": "Some Other City", + "postal_code": 5678 + } + ], + "contacts": [ + { + "type": "home", + "number": 123546789, + "is_default": false + }, + { + "type": "mobile", + "number": 987654321, + "is_default": true + } + ] + }, + { + "title": "Mrs.", + "name": "Smith", + "addresses": [ + { + "street": "Street Who?", + "number": 11, + "city": "City Who?", + "postal_code": 1111 + }, + { + "street": "Other Street Who?", + "number": 22, + "city": "Other City Who?", + "postal_code": 2222 + } + ], + "contacts": [ + { + "type": "home", + "number": 999999999999999, + "is_default": false + }, + { + "type": "mobile", + "number": 111111111111111, + "is_default": true + } + ] + } + ], + "title": "Mr.", + "name": "Smith", + "addresses": [{ + "street": "Some Street", + "number": 1, + "city": "Some City", + "postal_code": 1234 + }, { + "street": "Some Other Street", + "number": 2, + "city": "Some Other City", + "postal_code": 5678 + } + ], + "contacts": [{ + "type": "home", + "number": 123546789, + "is_default": false + }, { + "type": "mobile", + "number": 987654321, + "is_default": true + } + ] +} \ No newline at end of file diff --git a/UnitTests/JUST.net.UnitTests.csproj b/UnitTests/JUST.net.UnitTests.csproj index 0d6afec..6957013 100644 --- a/UnitTests/JUST.net.UnitTests.csproj +++ b/UnitTests/JUST.net.UnitTests.csproj @@ -10,8 +10,8 @@ - - + + @@ -32,6 +32,9 @@ Always + + Always + \ No newline at end of file diff --git a/UnitTests/LoadTests.cs b/UnitTests/LoadTests.cs index 26e1488..96ab7e1 100644 --- a/UnitTests/LoadTests.cs +++ b/UnitTests/LoadTests.cs @@ -15,7 +15,7 @@ public void LargeInput() const string transformer = "{ \"result\": { \"#loop($.list)\": { \"id\": \"#currentindex()\", \"name\": \"#concat(#currentvalueatpath($.title), #currentvalueatpath($.name))\", \"contact\": \"#currentvalueatpath($.contacts[?(@.is_default==true)])\", \"address\": \"#currentvalueatpath($.addresses[0])\" } }"; var w = Stopwatch.StartNew(); - new JsonTransformer().Transform(transformer, input); + new JsonTransformer(new JUSTContext { EvaluationMode = EvaluationMode.Strict }).Transform(transformer, input); w.Stop(); var timeConsumed = w.Elapsed; Assert.LessOrEqual(timeConsumed, TimeSpan.FromSeconds(5)); @@ -24,11 +24,11 @@ public void LargeInput() [Test] public void LargeTransformer() { - const string input = "{ \" title\" : \" Mr.\" , \" name\" : \" Smith\" , \" addresses\" : [ { \" street\" : \" Some Street\" , \" number\" : 1, \" city\" : \" Some City\" , \" postal_code\" : 1234 }, { \" street\" : \" Some Other Street\" , \" number\" : 2, \" city\" : \" Some Other City\" , \" postal_code\" : 5678 } ], \" contacts\" : [ { \" type\" : \" home\" , \" number\" : 123546789, \" is_default\" : false }, { \" type\" : \" mobile\" , \" number\" : 987654321, \" is_default\" : true } ] }"; + const string input = "{ \"title\": \"Mr.\", \"name\": \"Smith\", \"addresses\": [ { \"street\": \"Some Street\", \"number\": 1, \"city\": \"Some City\", \"postal_code\": 1234 }, { \"street\": \"Some Other Street\", \"number\": 2, \"city\": \"Some Other City\", \"postal_code\": 5678 } ], \"contacts\": [ { \"type\": \"home\", \"number\": 123546789, \"is_default\": false }, { \"type\": \"mobile\", \"number\": 987654321, \"is_default\": true } ] }"; var transformer = File.ReadAllText("Inputs/large_transformer.json"); var w = Stopwatch.StartNew(); - new JsonTransformer().Transform(transformer, input); + var e = new JsonTransformer(new JUSTContext { EvaluationMode = EvaluationMode.Strict }).Transform(transformer, input); w.Stop(); var timeConsumed = w.Elapsed; Assert.LessOrEqual(timeConsumed, TimeSpan.FromSeconds(5)); diff --git a/UnitTests/ThreadSafeTests.cs b/UnitTests/ThreadSafeTests.cs new file mode 100644 index 0000000..09053f2 --- /dev/null +++ b/UnitTests/ThreadSafeTests.cs @@ -0,0 +1,72 @@ +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using System.IO; +using System.Threading.Tasks; + +namespace JUST.UnitTests +{ + [TestFixture] + public class ThreadSafeTests + { + [Test] + public void TestTransforms1() + { + var input = File.ReadAllText("Inputs/thread_safe_input.json"); + const string transformer = "{ \"list\": { \"#loop($.list)\": { \"id\": \"#currentindex()\", \"name\": \"#concat(#currentvalueatpath($.title), #currentvalueatpath($.name))\", \"contact\": \"#currentvalueatpath($.contacts[?(@.is_default==true)])\", \"address\": \"#currentvalueatpath($.addresses[0])\" } }, \"applyover\": \"#applyover({ 'first_title': '#valueof($.list[0].title)'/, 'second_address': '#valueof($.list[1].addresses[1])'/, 'first_contact': '#valueof($.list[0].contacts[0])' }, '#valueof($.second_address.street)')\" }"; + const string expect = "{\"list\":[{\"id\":0,\"name\":\"Mr.Smith\",\"contact\":{\"type\":\"mobile\",\"number\":987654321,\"is_default\":true},\"address\":{\"street\":\"Some Street\",\"number\":1,\"city\":\"Some City\",\"postal_code\":1234}},{\"id\":1,\"name\":\"Mrs.Smith\",\"contact\":{\"type\":\"mobile\",\"number\":111111111111111,\"is_default\":true},\"address\":{\"street\":\"Street Who?\",\"number\":11,\"city\":\"City Who?\",\"postal_code\":1111}}],\"applyover\":\"Other Street Who?\"}"; + + JsonTransformer jsonTransformer = new JsonTransformer(new JUSTContext { EvaluationMode = EvaluationMode.Strict }); + + string transformResult = jsonTransformer.Transform(transformer, input); + Assert.AreEqual(expect, transformResult); + } + + [Test] + public void TestTransforms2() + { + var input = File.ReadAllText("Inputs/thread_safe_input.json"); + const string transformer = "{ \"result\": { \"name\": \"#concat(#valueof($.title), #valueof($.name))\", \"contact\": \"#valueof($.contacts[?(@.is_default==true)])\", \"address\": \"#valueof($.addresses[0])\" } }"; + const string expect = "{\"result\":{\"name\":\"Mr.Smith\",\"contact\":{\"type\":\"mobile\",\"number\":987654321,\"is_default\":true},\"address\":{\"street\":\"Some Street\",\"number\":1,\"city\":\"Some City\",\"postal_code\":1234}}}"; + + JsonTransformer jsonTransformer = new JsonTransformer(new JUSTContext { EvaluationMode = EvaluationMode.Strict }); + + string transformResult = jsonTransformer.Transform(transformer, input); + Assert.AreEqual(expect, transformResult); + } + + [Test] + public void ThreadSafe() + { + string threadSafeInput = File.ReadAllText("Inputs/thread_safe_input.json"); + string[] inputs = + { + threadSafeInput, + threadSafeInput, + ExampleInputs.Menu, + + }; + string[] transformers = + { + "{ \"list\": { \"#loop($.list)\": { \"id\": \"#currentindex()\", \"name\": \"#concat(#currentvalueatpath($.title), #currentvalueatpath($.name))\", \"contact\": \"#currentvalueatpath($.contacts[?(@.is_default==true)])\", \"address\": \"#currentvalueatpath($.addresses[0])\" } }, \"applyover\": \"#applyover({ 'first_title': '#valueof($.list[0].title)'/, 'second_address': '#valueof($.list[1].addresses[1])'/, 'first_contact': '#valueof($.list[0].contacts[0])' }, '#valueof($.second_address.street)')\" }", + "{ \"result\": { \"name\": \"#concat(#valueof($.title), #valueof($.name))\", \"contact\": \"#valueof($.contacts[?(@.is_default==true)])\", \"address\": \"#valueof($.addresses[0])\" } }", + "{\"root\": {\"menu1\": \"#valueof($.menu.popup.menuitem[?(@.value=='New')].onclick)\", \"menu2\": \"#valueof($.menu.popup.menuitem[?(@.value=='Open')].onclick)\"}}", + }; + string[] expects = + { + "{\"list\":[{\"id\":0,\"name\":\"Mr.Smith\",\"contact\":{\"type\":\"mobile\",\"number\":987654321,\"is_default\":true},\"address\":{\"street\":\"Some Street\",\"number\":1,\"city\":\"Some City\",\"postal_code\":1234}},{\"id\":1,\"name\":\"Mrs.Smith\",\"contact\":{\"type\":\"mobile\",\"number\":111111111111111,\"is_default\":true},\"address\":{\"street\":\"Street Who?\",\"number\":11,\"city\":\"City Who?\",\"postal_code\":1111}}],\"applyover\":\"Other Street Who?\"}", + "{\"result\":{\"name\":\"Mr.Smith\",\"contact\":{\"type\":\"mobile\",\"number\":987654321,\"is_default\":true},\"address\":{\"street\":\"Some Street\",\"number\":1,\"city\":\"Some City\",\"postal_code\":1234}}}", + "{\"root\":{\"menu1\":{\"action\":\"CreateNewDoc()\"},\"menu2\":\"OpenDoc()\"}}" + }; + + var jsonTransformer = new JsonTransformer(new JUSTContext { EvaluationMode = EvaluationMode.Strict }); + + ParallelLoopResult result = Parallel.For(0, 100, i => + { + JToken transformResult = jsonTransformer.Transform(JObject.Parse(transformers[i % 3]), inputs[i % 3]); + Assert.AreEqual(JObject.Parse(expects[i % 3]), transformResult); + }); + + Assert.IsTrue(result.IsCompleted); + } + } +}