Skip to content

Commit

Permalink
Add Dictionary support to JsonSerializer
Browse files Browse the repository at this point in the history
This partially addresses https://github.com/dotnet/corefx/issues/36024.
For the intial version, only `Dictionary<string, [some concrete type]>`
is supported.
  • Loading branch information
steveharter committed Apr 21, 2019
1 parent 13cd961 commit ae8d862
Show file tree
Hide file tree
Showing 16 changed files with 206 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ internal enum ClassType
Object = 1, // POCO or rich data type
Value = 2, // Data type with single value
Enumerable = 3, // IEnumerable
Dictionary = 4, // IDictionary
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ internal JsonClassInfo(Type type, JsonSerializerOptions options)
}
}
}
else if (ClassType == ClassType.Enumerable)
else if (ClassType == ClassType.Enumerable || ClassType == ClassType.Dictionary)
{
// Add a single property that maps to the class type so we can have policies applied.
AddProperty(type, propertyInfo : null, type, options);
Expand Down Expand Up @@ -311,9 +311,18 @@ public static Type GetElementType(Type propertyType)
elementType = propertyType.GetElementType();
if (elementType == null)
{
Type[] args = propertyType.GetGenericArguments();

if (propertyType.IsGenericType)
{
elementType = propertyType.GetGenericArguments()[0];
if (typeof(IDictionary).IsAssignableFrom(propertyType))
{
elementType = args[1];
}
else
{
elementType = args[0];
}
}
else
{
Expand All @@ -335,12 +344,17 @@ internal static ClassType GetClassType(Type type)
type = Nullable.GetUnderlyingType(type);
}

// A Type is considered a value if it implements IConvertible.
// A Type is considered a value if it implements IConvertible or is a DateTimeOffset.
if (typeof(IConvertible).IsAssignableFrom(type) || type == typeof(DateTimeOffset))
{
return ClassType.Value;
}

if (typeof(IDictionary).IsAssignableFrom(type))
{
return ClassType.Dictionary;
}

if (typeof(IEnumerable).IsAssignableFrom(type))
{
return ClassType.Enumerable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ internal TAttribute GetAttribute<TAttribute>() where TAttribute : Attribute

internal abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
internal abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
internal abstract void ReadDictionaryValue(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
internal abstract void SetValueAsObject(object obj, object value, JsonSerializerOptions options);

internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ internal JsonPropertyInfoNotNullable(

internal override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
{
if (ElementClassInfo != null)
if (state.Current.IsDictionary())
{
JsonPropertyInfo propertyInfo = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty();
propertyInfo.ReadDictionaryValue(tokenType, options, ref state, ref reader);
}
else if (ElementClassInfo != null)
{
// Forward the setter to the value-based JsonPropertyInfo.
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
Expand Down Expand Up @@ -75,6 +80,17 @@ internal override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOpt
JsonSerializer.ApplyValueToEnumerable(ref value, options, ref state.Current);
}

internal override void ReadDictionaryValue(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
{
if (ValueConverter == null || !ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value))
{
ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state);
return;
}

JsonSerializer.ApplyValueToDictionary(ref value, options, ref state.Current);
}

internal override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state)
{
Debug.Assert(state.Current.JsonPropertyInfo != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ internal JsonPropertyInfoNullable(

internal override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
{
if (ElementClassInfo != null)
if (state.Current.IsDictionary())
{
JsonPropertyInfo propertyInfo = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty();
propertyInfo.ReadDictionaryValue(tokenType, options, ref state, ref reader);
}
else if (ElementClassInfo != null)
{
// Forward the setter to the value-based JsonPropertyInfo.
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
Expand Down Expand Up @@ -73,6 +78,19 @@ internal override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOpt
JsonSerializer.ApplyValueToEnumerable(ref nullableValue, options, ref state.Current);
}

internal override void ReadDictionaryValue(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
{
if (ValueConverter == null || !ValueConverter.TryRead(typeof(TProperty), ref reader, out TProperty value))
{
ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state);
return;
}

// Converting to TProperty? here lets us share a common ApplyValue() with ApplyNullValue().
TProperty? nullableValue = new TProperty?(value);
JsonSerializer.ApplyValueToDictionary(ref nullableValue, options, ref state.Current);
}

internal override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state)
{
TProperty? nullableValue = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,20 @@ private static void HandleStartArray(
ref Utf8JsonReader reader,
ref ReadStack state)
{
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
JsonPropertyInfo jsonPropertyInfo;

if (state.Current.IsDictionary())
{
JsonClassInfo classInfo = state.Current.JsonClassInfo.ElementClassInfo;
JsonPropertyInfo propertyInfo = classInfo.GetPolicyProperty();

state.Push();
state.Current.JsonClassInfo = classInfo;
state.Current.JsonPropertyInfo = propertyInfo;
state.Current.IsNestedEnumerableInDict = true;
}

jsonPropertyInfo = state.Current.JsonPropertyInfo;

bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize;
if (skip || state.Current.Skip())
Expand Down Expand Up @@ -121,6 +134,16 @@ private static bool HandleEndArray(
state.Pop();
}

if (state.Current.IsNestedEnumerableInDict)
{
state.Pop();

Debug.Assert(state.Current.IsDictionary());
ApplyObjectToDictionary(value, options, ref state.Current);

return false;
}

if (lastFrame)
{
if (state.Current.ReturnValue == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, J
ThrowHelper.ThrowJsonReaderException_DeserializeCannotBeNull(reader, state);
}

if (state.Current.IsDictionary())
{
ApplyObjectToDictionary(null, options, ref state.Current);
return false;
}

if (state.Current.IsEnumerable())
{
ApplyObjectToEnumerable(null, options, ref state.Current);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace System.Text.Json.Serialization
{
Expand All @@ -22,8 +24,30 @@ private static void HandleStartObject(JsonSerializerOptions options, ref ReadSta
// An array of objects either on the current property or on a list
Type objType = state.Current.GetElementType();
state.Push();

state.Current.JsonClassInfo = options.GetOrAddClass(objType);
}
else if (state.Current.IsDictionary())
{
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;

bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize;
if (skip || state.Current.Skip())
{
// The dictionary is not being applied to the object.
state.Push();
state.Current.Drain = true;
return;
}

Type elementType = state.Current.JsonClassInfo.ElementClassInfo.Type;

state.Current.TempDictKeys = new List<string>();
state.Current.TempDictValues = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
state.Current.ReturnValue = (IDictionary)Activator.CreateInstance(state.Current.JsonClassInfo.Type);

return;
}
else if (state.Current.JsonPropertyInfo != null)
{
// Nested object
Expand All @@ -47,6 +71,23 @@ private static bool HandleEndObject(JsonSerializerOptions options, ref ReadStack

state.Current.JsonClassInfo.UpdateSortedPropertyCache(ref state.Current);

if (state.Current.IsDictionary())
{
ReadStackFrame frame = state.Current;

int keyCount = ((IList)frame.TempDictKeys).Count;

Debug.Assert(keyCount == (frame.TempDictValues).Count);

IList keys = frame.TempDictKeys;
IList values = frame.TempDictValues;

for (int i = 0; i < keyCount; i++)
{
((IDictionary)state.Current.ReturnValue).Add(keys[i], values[i]);
}
}

object value = state.Current.ReturnValue;

if (isLastFrame)
Expand All @@ -60,5 +101,19 @@ private static bool HandleEndObject(JsonSerializerOptions options, ref ReadStack
ApplyObjectToEnumerable(value, options, ref state.Current);
return false;
}

internal static void ApplyValueToDictionary<TProperty>(ref TProperty value, JsonSerializerOptions options, ref ReadStackFrame frame)
{
Debug.Assert(frame.IsDictionary());
((List<TProperty>)frame.TempDictValues).Add(value);
Debug.Assert(((IList)frame.TempDictKeys).Count == ((IList)frame.TempDictValues).Count);
}

internal static void ApplyObjectToDictionary(object value, JsonSerializerOptions options, ref ReadStackFrame frame)
{
Debug.Assert(frame.IsDictionary());
(frame.TempDictValues).Add(value);
Debug.Assert(((IList)frame.TempDictKeys).Count == ((IList)frame.TempDictValues).Count);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ private static bool HandleValue(JsonTokenType tokenType, JsonSerializerOptions o
jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options);
}

bool lastCall = (!state.Current.IsEnumerable() && !state.Current.IsPropertyEnumerable() && state.Current.ReturnValue == null);
bool notParsingEnumerable = !state.Current.IsEnumerable() && !state.Current.IsPropertyEnumerable();
bool lastCall = (notParsingEnumerable && !state.Current.IsDictionary() && state.Current.ReturnValue == null);

jsonPropertyInfo.Read(tokenType, options, ref state, ref reader);
return lastCall;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;

namespace System.Text.Json.Serialization
Expand Down Expand Up @@ -42,13 +43,28 @@ private static void ReadCore(
Debug.Assert(state.Current.JsonClassInfo != default);

ReadOnlySpan<byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(options, propertyName, ref state.Current);
if (state.Current.JsonPropertyInfo == null)

if (state.Current.IsDictionary())
{
state.Current.JsonPropertyInfo = s_missingProperty;
string keyName = reader.GetString();

if (options.DictionaryKeyPolicy != null)
{
keyName = options.DictionaryKeyPolicy.ConvertName(keyName);
}

state.Current.TempDictKeys.Add(keyName);
}
else
{
state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(options, propertyName, ref state.Current);
if (state.Current.JsonPropertyInfo == null)
{
state.Current.JsonPropertyInfo = s_missingProperty;
}

state.Current.PropertyIndex++;
state.Current.PropertyIndex++;
}
}
}
else if (tokenType == JsonTokenType.StartObject)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ private static bool Write(
finishedSerializing = true;
break;
case ClassType.Object:
case ClassType.Dictionary:
finishedSerializing = WriteObject(options, writer, ref state);
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ internal struct ReadStackFrame
internal object ReturnValue;
internal JsonClassInfo JsonClassInfo;

// Support Dictionary
internal List<string> TempDictKeys;
internal IList TempDictValues;
internal bool IsNestedEnumerableInDict;

// Current property values
internal JsonPropertyInfo JsonPropertyInfo;
internal bool PopStackOnEndArray;
Expand All @@ -32,7 +37,7 @@ internal struct ReadStackFrame
internal void Initialize(Type type, JsonSerializerOptions options)
{
JsonClassInfo = options.GetOrAddClass(type);
if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable)
if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary)
{
JsonPropertyInfo = JsonClassInfo.GetPolicyProperty();
}
Expand Down Expand Up @@ -61,6 +66,11 @@ internal bool IsEnumerable()
return JsonClassInfo.ClassType == ClassType.Enumerable;
}

internal bool IsDictionary()
{
return JsonClassInfo.ClassType == ClassType.Dictionary;
}

internal bool Skip()
{
return Drain || ReferenceEquals(JsonPropertyInfo, JsonSerializer.s_missingProperty);
Expand All @@ -76,6 +86,16 @@ internal bool IsPropertyEnumerable()
return false;
}

internal bool IsPropertyADictionary()
{
if (JsonPropertyInfo != null)
{
return JsonPropertyInfo.ClassType == ClassType.Dictionary;
}

return false;
}

public Type GetElementType()
{
if (IsPropertyEnumerable())
Expand All @@ -88,6 +108,11 @@ public Type GetElementType()
return JsonClassInfo.ElementClassInfo.Type;
}

if (IsDictionary())
{
return JsonClassInfo.ElementClassInfo.Type;
}

return JsonPropertyInfo.RuntimePropertyType;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void Push(JsonClassInfo nextClassInfo, object nextValue)
}
else
{
Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Unknown);
Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Dictionary || nextClassInfo.ClassType == ClassType.Unknown);
Current.PopStackOnEndObject = true;
}
}
Expand Down
Loading

0 comments on commit ae8d862

Please sign in to comment.