JsonParser: Casts arrays to better tailored lists, instead of List<object>.
This commit is contained in:
@@ -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()]
|
||||
|
||||
@@ -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 '[':
|
||||
|
||||
Reference in New Issue
Block a user