Skip to content

Commit

Permalink
Support unlimited array dimensions (dotnet#37867)
Browse files Browse the repository at this point in the history
  • Loading branch information
steveharter authored May 23, 2019
1 parent 41489a9 commit 025bb33
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 201 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ private static void HandleStartArray(
if (state.Current.PropertyInitialized)
{
// A nested json array so push a new stack frame.
Type elementType = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty().RuntimePropertyType;
Type elementType = jsonPropertyInfo.ElementClassInfo.Type;

state.Push();
state.Current.Initialize(elementType, options);
state.Current.PropertyInitialized = true;
}
else
{
Expand All @@ -64,6 +65,8 @@ private static void HandleStartArray(
{
// Create the enumerable.
object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state, options);

// If value is not null, then we don't have a converter so apply the value.
if (value != null)
{
if (state.Current.ReturnValue != null)
Expand Down Expand Up @@ -94,26 +97,27 @@ private static bool HandleEndArray(
}

IEnumerable value = ReadStackFrame.GetEnumerableValue(state.Current);
bool setPropertyDirectly;

if (state.Current.TempEnumerableValues != null)
{
// We have a converter; possibilities:
// - Add value to current frame's current property or TempEnumerableValues.
// - Add value to previous frame's current property or TempEnumerableValues.
// - Set current property on current frame to value.
// - Set current property on previous frame to value.
// - Set ReturnValue if root frame and value is the actual return value.
JsonEnumerableConverter converter = state.Current.JsonPropertyInfo.EnumerableConverter;
Debug.Assert(converter != null);

value = converter.CreateFromList(ref state, (IList)value, options);
setPropertyDirectly = true;
state.Current.TempEnumerableValues = null;
}
else if (state.Current.IsEnumerableProperty)
{
// We added the items to the list already.
state.Current.ResetProperty();
return false;
}
else
{
setPropertyDirectly = false;
}

if (lastFrame)
{
Expand All @@ -136,12 +140,7 @@ private static bool HandleEndArray(
state.Pop();
}

ApplyObjectToEnumerable(value, ref state, ref reader, setPropertyDirectly: setPropertyDirectly);

if (state.Current.IsEnumerableProperty)
{
state.Current.ResetProperty();
}
ApplyObjectToEnumerable(value, ref state, ref reader);

return false;
}
Expand Down Expand Up @@ -177,8 +176,14 @@ internal static void ApplyObjectToEnumerable(
else
{
IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
Debug.Assert(list != null);
list.Add(value);
if (list == null)
{
state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
}
else
{
list.Add(value);
}
}
}
else if (state.Current.IsDictionary || (state.Current.IsDictionaryProperty && !setPropertyDirectly))
Expand Down Expand Up @@ -234,8 +239,14 @@ internal static void ApplyValueToEnumerable<TProperty>(
else
{
IList<TProperty> list = (IList<TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
Debug.Assert(list != null);
list.Add(value);
if (list == null)
{
state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
}
else
{
list.Add(value);
}
}
}
else if (state.Current.IsProcessingDictionary)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ private static void HandleStartDictionary(JsonSerializerOptions options, ref Utf
{
Debug.Assert(state.Current.IsDictionary);

JsonClassInfo classInfoTemp = state.Current.JsonClassInfo;
state.Push();
state.Current.JsonClassInfo = classInfoTemp.ElementClassInfo;
state.Current.JsonClassInfo = jsonPropertyInfo.ElementClassInfo;
state.Current.InitializeJsonPropertyInfo();
state.Current.PropertyInitialized = true;

ClassType classType = state.Current.JsonClassInfo.ClassType;
if (classType == ClassType.Value &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ private static void HandlePropertyName(
}
else
{
state.Current.ResetProperty();

ReadOnlySpan<byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
if (reader._stringHasEscaping)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public void ResetProperty()
PropertyInitialized = false;
JsonPropertyInfo = null;
TempEnumerableValues = null;
KeyName = null;
}

public void EndObject()
Expand Down
95 changes: 93 additions & 2 deletions src/System.Text.Json/tests/Serialization/Array.ReadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,74 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection;
using Xunit;

namespace System.Text.Json.Serialization.Tests
{
public static partial class ArrayTests
{
[Fact]
public static void ReadObjectArray()
{
string data =
"[" +
SimpleTestClass.s_json +
"," +
SimpleTestClass.s_json +
"]";

SimpleTestClass[] i = JsonSerializer.Parse<SimpleTestClass[]>(Encoding.UTF8.GetBytes(data));

i[0].Verify();
i[1].Verify();
}

[Fact]
public static void ReadEmptyObjectArray()
{
SimpleTestClass[] data = JsonSerializer.Parse<SimpleTestClass[]>("[{}]");
Assert.Equal(1, data.Length);
Assert.NotNull(data[0]);
}

[Fact]
public static void ReadPrimitiveJagged2dArray()
{
int[][] i = JsonSerializer.Parse<int[][]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
Assert.Equal(1, i[0][0]);
Assert.Equal(2, i[0][1]);
Assert.Equal(3, i[1][0]);
Assert.Equal(4, i[1][1]);
}

[Fact]
public static void ReadPrimitiveJagged3dArray()
{
int[][][] i = JsonSerializer.Parse<int[][][]>(Encoding.UTF8.GetBytes(@"[[[11,12],[13,14]], [[21,22],[23,24]]]"));
Assert.Equal(11, i[0][0][0]);
Assert.Equal(12, i[0][0][1]);
Assert.Equal(13, i[0][1][0]);
Assert.Equal(14, i[0][1][1]);

Assert.Equal(21, i[1][0][0]);
Assert.Equal(22, i[1][0][1]);
Assert.Equal(23, i[1][1][0]);
Assert.Equal(24, i[1][1][1]);
}

[Fact]
public static void ReadArrayWithInterleavedComments()
{
var options = new JsonSerializerOptions();
options.ReadCommentHandling = JsonCommentHandling.Skip;

int[][] i = JsonSerializer.Parse<int[][]>(Encoding.UTF8.GetBytes("[[1,2] // Inline [\n,[3, /* Multi\n]] Line*/4]]"), options);
Assert.Equal(1, i[0][0]);
Assert.Equal(2, i[0][1]);
Assert.Equal(3, i[1][0]);
Assert.Equal(4, i[1][1]);
}

[Fact]
public static void ReadEmpty()
{
Expand All @@ -21,6 +81,37 @@ public static void ReadEmpty()
Assert.Equal(0, list.Count);
}

[Fact]
public static void ReadPrimitiveArray()
{
int[] i = JsonSerializer.Parse<int[]>(Encoding.UTF8.GetBytes(@"[1,2]"));
Assert.Equal(1, i[0]);
Assert.Equal(2, i[1]);

i = JsonSerializer.Parse<int[]>(Encoding.UTF8.GetBytes(@"[]"));
Assert.Equal(0, i.Length);
}

[Fact]
public static void ReadArrayWithEnums()
{
SampleEnum[] i = JsonSerializer.Parse<SampleEnum[]>(Encoding.UTF8.GetBytes(@"[1,2]"));
Assert.Equal(SampleEnum.One, i[0]);
Assert.Equal(SampleEnum.Two, i[1]);
}

[Fact]
public static void ReadPrimitiveArrayFail()
{
// Invalid data
Assert.Throws<JsonException>(() => JsonSerializer.Parse<int[]>(Encoding.UTF8.GetBytes(@"[1,""a""]")));

// Invalid data
Assert.Throws<JsonException>(() => JsonSerializer.Parse<List<int?>>(Encoding.UTF8.GetBytes(@"[1,""a""]")));

// Multidimensional arrays currently not supported
Assert.Throws<JsonException>(() => JsonSerializer.Parse<int[,]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")));
}

public static IEnumerable<object[]> ReadNullJson
{
Expand Down
60 changes: 60 additions & 0 deletions src/System.Text.Json/tests/Serialization/Array.WriteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,66 @@ namespace System.Text.Json.Serialization.Tests
{
public static partial class ArrayTests
{
[Fact]
public static void WritePrimitiveArray()
{
var input = new int[] { 0, 1 };
string json = JsonSerializer.ToString(input);
Assert.Equal("[0,1]", json);
}

[Fact]
public static void WriteArrayWithEnums()
{
var input = new SampleEnum[] { SampleEnum.One, SampleEnum.Two };
string json = JsonSerializer.ToString(input);
Assert.Equal("[1,2]", json);
}

[Fact]
public static void WriteObjectArray()
{
string json;

{
SimpleTestClass[] input = new SimpleTestClass[] { new SimpleTestClass(), new SimpleTestClass() };
input[0].Initialize();
input[0].Verify();

input[1].Initialize();
input[1].Verify();

json = JsonSerializer.ToString(input);
}

{
SimpleTestClass[] output = JsonSerializer.Parse<SimpleTestClass[]>(json);
Assert.Equal(2, output.Length);
output[0].Verify();
output[1].Verify();
}
}

[Fact]
public static void WriteEmptyObjectArray()
{
object[] arr = new object[] { new object() };

string json = JsonSerializer.ToString(arr);
Assert.Equal("[{}]", json);
}

[Fact]
public static void WritePrimitiveJaggedArray()
{
var input = new int[2][];
input[0] = new int[] { 1, 2 };
input[1] = new int[] { 3, 4 };

string json = JsonSerializer.ToString(input);
Assert.Equal("[[1,2],[3,4]]", json);
}

[Fact]
public static void WriteEmpty()
{
Expand Down
66 changes: 66 additions & 0 deletions src/System.Text.Json/tests/Serialization/DictionaryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,72 @@ public static void DictionaryOfDictionary()
Assert.Equal(JsonString, json);
}

[Fact]
public static void DictionaryOfDictionaryOfDictionary()
{
const string JsonString = @"{""Key1"":{""Key1"":{""Key1"":1,""Key2"":2},""Key2"":{""Key1"":3,""Key2"":4}},""Key2"":{""Key1"":{""Key1"":5,""Key2"":6},""Key2"":{""Key1"":7,""Key2"":8}}}";
Dictionary<string, Dictionary<string, Dictionary<string, int>>> obj = JsonSerializer.Parse<Dictionary<string, Dictionary<string, Dictionary<string, int>>>>(JsonString);

Assert.Equal(2, obj.Count);
Assert.Equal(2, obj["Key1"].Count);
Assert.Equal(2, obj["Key1"]["Key1"].Count);
Assert.Equal(2, obj["Key1"]["Key2"].Count);

Assert.Equal(1, obj["Key1"]["Key1"]["Key1"]);
Assert.Equal(2, obj["Key1"]["Key1"]["Key2"]);
Assert.Equal(3, obj["Key1"]["Key2"]["Key1"]);
Assert.Equal(4, obj["Key1"]["Key2"]["Key2"]);

Assert.Equal(2, obj["Key2"].Count);
Assert.Equal(2, obj["Key2"]["Key1"].Count);
Assert.Equal(2, obj["Key2"]["Key2"].Count);

Assert.Equal(5, obj["Key2"]["Key1"]["Key1"]);
Assert.Equal(6, obj["Key2"]["Key1"]["Key2"]);
Assert.Equal(7, obj["Key2"]["Key2"]["Key1"]);
Assert.Equal(8, obj["Key2"]["Key2"]["Key2"]);

string json = JsonSerializer.ToString(obj);
Assert.Equal(JsonString, json);

// Verify that typeof(object) doesn't interfere.
json = JsonSerializer.ToString<object>(obj);
Assert.Equal(JsonString, json);
}

[Fact]
public static void DictionaryOfArrayOfDictionary()
{
const string JsonString = @"{""Key1"":[{""Key1"":1,""Key2"":2},{""Key1"":3,""Key2"":4}],""Key2"":[{""Key1"":5,""Key2"":6},{""Key1"":7,""Key2"":8}]}";
Dictionary<string, Dictionary<string, int>[]> obj = JsonSerializer.Parse<Dictionary<string, Dictionary<string, int>[]>>(JsonString);

Assert.Equal(2, obj.Count);
Assert.Equal(2, obj["Key1"].Length);
Assert.Equal(2, obj["Key1"][0].Count);
Assert.Equal(2, obj["Key1"][1].Count);

Assert.Equal(1, obj["Key1"][0]["Key1"]);
Assert.Equal(2, obj["Key1"][0]["Key2"]);
Assert.Equal(3, obj["Key1"][1]["Key1"]);
Assert.Equal(4, obj["Key1"][1]["Key2"]);

Assert.Equal(2, obj["Key2"].Length);
Assert.Equal(2, obj["Key2"][0].Count);
Assert.Equal(2, obj["Key2"][1].Count);

Assert.Equal(5, obj["Key2"][0]["Key1"]);
Assert.Equal(6, obj["Key2"][0]["Key2"]);
Assert.Equal(7, obj["Key2"][1]["Key1"]);
Assert.Equal(8, obj["Key2"][1]["Key2"]);

string json = JsonSerializer.ToString(obj);
Assert.Equal(JsonString, json);

// Verify that typeof(object) doesn't interfere.
json = JsonSerializer.ToString<object>(obj);
Assert.Equal(JsonString, json);
}

[Fact]
public static void DictionaryOfClasses()
{
Expand Down
Loading

0 comments on commit 025bb33

Please sign in to comment.