From d3c6e34350c3e711da122bd88f9247d06354613e Mon Sep 17 00:00:00 2001 From: "Valeriano A.R" Date: Sun, 6 Sep 2020 23:11:47 +0200 Subject: [PATCH] JsonParser: Casts arrays to better tailored lists, instead of List. --- VAR.Json.Tests/JsonParser_Tests.cs | 116 ++++++++++++++++++++++++++++- VAR.Json/JsonParser.cs | 44 +++++++++-- 2 files changed, 151 insertions(+), 9 deletions(-) diff --git a/VAR.Json.Tests/JsonParser_Tests.cs b/VAR.Json.Tests/JsonParser_Tests.cs index d346f63..965ba0b 100644 --- a/VAR.Json.Tests/JsonParser_Tests.cs +++ b/VAR.Json.Tests/JsonParser_Tests.cs @@ -1,10 +1,124 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace VAR.Json.Tests { [TestClass()] public class JsonParser_Tests { + #region Parse + + public class SwallowObject + { + public string Text { get; set; } + public int Number { get; set; } + } + + [TestMethod()] + public void Parse__SwallowObject() + { + JsonParser parser = new JsonParser(); + parser.KnownTypes.Add(typeof(SwallowObject)); + SwallowObject result = parser.Parse(@"{""Text"": ""AAAA"", ""Number"": 42}") as SwallowObject; + Assert.AreEqual(false, parser.Tainted); + Assert.AreEqual("AAAA", result.Text); + Assert.AreEqual(42, result.Number); + } + + public class DeeperObject_L1 + { + public string Name { get; set; } + public SwallowObject Object { get; set; } + } + + [TestMethod()] + public void Parse__DeeperObject_L1() + { + JsonParser parser = new JsonParser(); + parser.KnownTypes.Add(typeof(SwallowObject)); + parser.KnownTypes.Add(typeof(DeeperObject_L1)); + DeeperObject_L1 result = parser.Parse(@"{""Name"": ""Thing"", ""Object"": {""Text"": ""AAAA"", ""Number"": 42}}") as DeeperObject_L1; + Assert.AreEqual(false, parser.Tainted); + Assert.AreEqual("Thing", result.Name); + Assert.AreEqual("AAAA", result.Object.Text); + Assert.AreEqual(42, result.Object.Number); + } + + public class DeeperObject_L2 + { + public int Count { get; set; } + public DeeperObject_L1 Object { get; set; } + } + + [TestMethod()] + public void Parse__DeeperObject_L2() + { + JsonParser parser = new JsonParser(); + parser.KnownTypes.Add(typeof(SwallowObject)); + parser.KnownTypes.Add(typeof(DeeperObject_L1)); + parser.KnownTypes.Add(typeof(DeeperObject_L2)); + DeeperObject_L2 result = parser.Parse(@"{""Count"": 1, ""Object"": {""Name"": ""Thing"", ""Object"": {""Text"": ""AAAA"", ""Number"": 42}}}") as DeeperObject_L2; + Assert.AreEqual(false, parser.Tainted); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Thing", result.Object.Name); + Assert.AreEqual("AAAA", result.Object.Object.Text); + Assert.AreEqual(42, result.Object.Object.Number); + } + + [TestMethod()] + public void Parse__SwallowObjectArray() + { + JsonParser parser = new JsonParser(); + parser.KnownTypes.Add(typeof(SwallowObject)); + List result = parser.Parse(@"[{""Text"": ""AAAA"", ""Number"": 42}]") as List; + Assert.AreEqual(false, parser.Tainted); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("AAAA", result[0].Text); + Assert.AreEqual(42, result[0].Number); + } + + public class DeeperObjectArray_L1 + { + public int Count { get; set; } + public List Array { get; set; } + } + + [TestMethod()] + public void Parse__DeeperObjectArray_L1() + { + JsonParser parser = new JsonParser(); + parser.KnownTypes.Add(typeof(SwallowObject)); + parser.KnownTypes.Add(typeof(DeeperObjectArray_L1)); + DeeperObjectArray_L1 result = parser.Parse(@"{""Count"": 1, ""Array"": [{""Text"": ""AAAA"", ""Number"": 42}]}") as DeeperObjectArray_L1; + Assert.AreEqual(false, parser.Tainted); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("AAAA", result.Array[0].Text); + Assert.AreEqual(42, result.Array[0].Number); + } + + public class DeeperObjectArray_L2 + { + public string Name { get; set; } + public List Objects { get; set; } + } + + [TestMethod()] + public void Parse__DeeperObjectArray_L2() + { + JsonParser parser = new JsonParser(); + parser.KnownTypes.Add(typeof(SwallowObject)); + parser.KnownTypes.Add(typeof(DeeperObjectArray_L1)); + parser.KnownTypes.Add(typeof(DeeperObjectArray_L2)); + DeeperObjectArray_L2 result = parser.Parse(@"{""Name"": ""Thing"", ""Objects"": [{""Count"": 1, ""Array"": [{""Text"": ""AAAA"", ""Number"": 42}]}]}") as DeeperObjectArray_L2; + Assert.AreEqual(false, parser.Tainted); + Assert.AreEqual("Thing", result.Name); + Assert.AreEqual(1, result.Objects[0].Count); + Assert.AreEqual("AAAA", result.Objects[0].Array[0].Text); + Assert.AreEqual(42, result.Objects[0].Array[0].Number); + } + + #endregion Parse + #region Validity tests [TestMethod()] diff --git a/VAR.Json/JsonParser.cs b/VAR.Json/JsonParser.cs index 6aa4d30..1c16467 100644 --- a/VAR.Json/JsonParser.cs +++ b/VAR.Json/JsonParser.cs @@ -421,7 +421,7 @@ namespace VAR.Json } } - private List ParseArray(int recursiveCount = 1) + private object ParseArray(int recursiveCount = 1) { // StrictRules: Mark as tainted when MaxRecursiveCount is exceeded if (recursiveCount >= MaxRecursiveCount) { _tainted = true; } @@ -429,6 +429,9 @@ namespace VAR.Json bool correct = false; char c = _ctx.SkipWhite(); List array = new List(); + Type arrayContentType = null; + bool hasSameType = true; + bool hasNulls = false; if (c == '[') { _ctx.Next(); @@ -457,19 +460,44 @@ namespace VAR.Json { // StrictRules: Mark as tainted when unexpected value on array if (expectValue == false) { _tainted = true; } - - array.Add(ParseValue(recursiveCount + 1)); + object value = ParseValue(recursiveCount + 1); + array.Add(value); expectValue = false; + + if (hasSameType) + { + Type valueType = value?.GetType(); + if (valueType == null) { hasNulls = true; } + if (arrayContentType == null || arrayContentType == valueType) + { + arrayContentType = valueType; + } + else + { + hasSameType = false; + } + } } } while (!_ctx.AtEnd()); if (correct == false) { _tainted = true; } - return array; + object result = array; + bool isNullableType = arrayContentType?.IsClass == true; + if (hasSameType && arrayContentType != null && (isNullableType == true || (isNullableType == false && hasNulls == false))) + { + var enumerableType = typeof(System.Linq.Enumerable); + var castMethod = enumerableType.GetMethod("Cast").MakeGenericMethod(arrayContentType); + var toListMethod = enumerableType.GetMethod("ToList").MakeGenericMethod(arrayContentType); + IEnumerable itemsToCast = array; + var castedItems = castMethod.Invoke(null, new[] { itemsToCast }); + result = toListMethod.Invoke(null, new[] { castedItems }); + } + return result; } - private Dictionary ParseObject(int recursiveCount = 1) + private object ParseObject(int recursiveCount = 1) { // StrictRules: Mark as tainted when MaxRecursiveCount is exceeded if (recursiveCount >= MaxRecursiveCount) { _tainted = true; } @@ -538,7 +566,8 @@ namespace VAR.Json { _tainted = true; } - return obj; + object result = TryConvertToTypes(obj); + return result; } private object ParseValue(int recusiveCount = 1) @@ -558,8 +587,7 @@ namespace VAR.Json break; case '{': - Dictionary obj = ParseObject(recusiveCount); - token = TryConvertToTypes(obj); + token = ParseObject(recusiveCount); break; case '[':