JsonParser: Casts arrays to better tailored lists, instead of List<object>.

This commit is contained in:
2020-09-06 23:11:47 +02:00
parent 8382f7f9ea
commit d3c6e34350
2 changed files with 151 additions and 9 deletions

View File

@@ -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<SwallowObject> result = parser.Parse(@"[{""Text"": ""AAAA"", ""Number"": 42}]") as List<SwallowObject>;
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<SwallowObject> 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<DeeperObjectArray_L1> 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()]

View File

@@ -421,7 +421,7 @@ namespace VAR.Json
}
}
private List<object> 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<object> array = new List<object>();
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<object> itemsToCast = array;
var castedItems = castMethod.Invoke(null, new[] { itemsToCast });
result = toListMethod.Invoke(null, new[] { castedItems });
}
return result;
}
private Dictionary<string, object> 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<string, object> obj = ParseObject(recusiveCount);
token = TryConvertToTypes(obj);
token = ParseObject(recusiveCount);
break;
case '[':