Skip to content

Commit

Permalink
Support json deserialize of 'null' and 'bool' when declared type is t…
Browse files Browse the repository at this point in the history
…ypeof(object) (dotnet#36637)

* Support null and bool values for typeof(object)

* Remove support for bool
  • Loading branch information
steveharter authored and ahsonkhan committed Apr 6, 2019
1 parent 2862fae commit 35249a0
Show file tree
Hide file tree
Showing 28 changed files with 286 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ namespace System.Text.Json.Serialization
/// </summary>
internal enum ClassType
{
Object = 0,
Value = 1,
Enumerable = 2,
Unknown = 0, // typeof(object)
Object = 1, // POCO or rich data type
Value = 2, // Data type with single value
Enumerable = 3, // IEnumerable
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ internal static object Create(Type type)
{
return new JsonValueConverterDateTimeOffset();
}

return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,17 @@ internal JsonPropertyInfo CreateProperty(Type declaredPropertyType, Type runtime

internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options)
{
// For now we only support typeof(object) for polymorphism.
Debug.Assert(property?.DeclaredPropertyType == typeof(object));
Debug.Assert(runtimePropertyType != typeof(object));
if (property == null)
{
// Used with root objects which are not really a property.
return CreateProperty(runtimePropertyType, runtimePropertyType, null, runtimePropertyType, options);
}

JsonPropertyInfo runtimeProperty = CreateProperty(property.DeclaredPropertyType, runtimePropertyType, property?.PropertyInfo, Type, options);

runtimeProperty._name = property._name;
runtimeProperty._escapedName = property._escapedName;
// Copy other settings here as they are added as features.

return runtimeProperty;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,16 @@ internal JsonClassInfo(Type type, JsonSerializerOptions options)
Type elementType = GetElementType(type);
ElementClassInfo = options.GetOrAddClass(elementType);
}
else
else if (ClassType == ClassType.Value)
{
Debug.Assert(ClassType == ClassType.Value);

// Add a single property that maps to the class type so we can have policies applied.
AddProperty(type, propertyInfo: null, type, options);
}
else
{
Debug.Assert(ClassType == ClassType.Unknown);
// Do nothing. The type is typeof(object).
}
}

internal JsonPropertyInfo GetProperty(ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame)
Expand Down Expand Up @@ -315,6 +318,11 @@ internal static ClassType GetClassType(Type type)
return ClassType.Enumerable;
}

if (type == typeof(object))
{
return ClassType.Unknown;
}

return ClassType.Object;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ private static void HandleStartArray(
return;
}

Type arrayType = state.Current.JsonPropertyInfo.RuntimePropertyType;
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
if (jsonPropertyInfo == null || state.Current.JsonClassInfo.ClassType == ClassType.Unknown)
{
jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options);
}

Type arrayType = jsonPropertyInfo.RuntimePropertyType;
if (!typeof(IEnumerable).IsAssignableFrom(arrayType) || (arrayType.IsArray && arrayType.GetArrayRank() > 1))
{
ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(arrayType, reader, state);
Expand All @@ -39,8 +45,7 @@ private static void HandleStartArray(

state.Push();

state.Current.JsonClassInfo = options.GetOrAddClass(elementType);
state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetPolicyProperty();
state.Current.Initialize(elementType, options);
state.Current.PopStackOnEndArray = true;
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;

namespace System.Text.Json.Serialization
{
public static partial class JsonSerializer
Expand All @@ -13,6 +15,14 @@ private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, J
return false;
}

// If we don't have a valid property, that means we read "null" for a root object so just return.
if (state.Current.JsonPropertyInfo == null)
{
Debug.Assert(state.IsLastFrame);
Debug.Assert(state.Current.ReturnValue == null);
return true;
}

JsonPropertyInfo propertyInfo = state.Current.JsonPropertyInfo;
if (!propertyInfo.CanBeNull)
{
Expand All @@ -27,6 +37,7 @@ private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, J

if (state.Current.ReturnValue == null)
{
Debug.Assert(state.IsLastFrame);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ private static bool HandleValue(JsonTokenType tokenType, JsonSerializerOptions o
return false;
}

JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
if (jsonPropertyInfo == null || state.Current.JsonClassInfo.ClassType == ClassType.Unknown)
{
jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options);
}

bool lastCall = (!state.Current.IsEnumerable() && !state.Current.IsPropertyEnumerable() && state.Current.ReturnValue == null);
state.Current.JsonPropertyInfo.Read(tokenType, options, ref state, ref reader);
jsonPropertyInfo.Read(tokenType, options, ref state, ref reader);
return lastCall;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,7 @@ private static async ValueTask<TValue> ReadAsync<TValue>(
options ??= s_defaultSettings;

ReadStack state = default;
JsonClassInfo classInfo = options.GetOrAddClass(returnType);
state.Current.JsonClassInfo = classInfo;
if (classInfo.ClassType != ClassType.Object)
{
state.Current.JsonPropertyInfo = classInfo.GetPolicyProperty();
}
state.Current.Initialize(returnType, options);

var readerState = new JsonReaderState(options.ReaderOptions);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ private static object ReadCore(
options = s_defaultSettings;

ReadStack state = default;
JsonClassInfo classInfo = options.GetOrAddClass(returnType);
state.Current.JsonClassInfo = classInfo;
if (classInfo.ClassType != ClassType.Object)
{
state.Current.JsonPropertyInfo = classInfo.GetPolicyProperty();
}
state.Current.Initialize(returnType, options);

ReadCore(options, ref reader, ref state);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,11 @@ private static bool HandleEnumerable(

if (state.Current.Enumerator != null && state.Current.Enumerator.MoveNext())
{
// If the enumerator contains typeof(object), get the run-time type
if (elementClassInfo.ClassType == ClassType.Object && jsonPropertyInfo.ElementClassInfo.Type == typeof(object))
// Check for polymorphism.
if (elementClassInfo.ClassType == ClassType.Unknown)
{
object currentValue = state.Current.Enumerator.Current;
if (currentValue != null)
{
Type runtimeType = currentValue.GetType();

// Ignore object() instances since they are handled as an empty object.
if (runtimeType != typeof(object))
{
elementClassInfo = options.GetOrAddClass(runtimeType);
}
}
GetRuntimeClassInfo(currentValue, ref elementClassInfo, options);
}

if (elementClassInfo.ClassType == ClassType.Value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,46 +55,34 @@ private static bool HandleObject(
ref Utf8JsonWriter writer,
ref WriteStack state)
{
Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);
Debug.Assert(
state.Current.JsonClassInfo.ClassType == ClassType.Object ||
state.Current.JsonClassInfo.ClassType == ClassType.Unknown);

JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(state.Current.PropertyIndex);
ClassType propertyClassType = jsonPropertyInfo.ClassType;

bool obtainedValue = false;
object currentValue = null;

// Check for polymorphism.
if (jsonPropertyInfo.RuntimePropertyType == typeof(object))
if (jsonPropertyInfo.ClassType == ClassType.Unknown)
{
Debug.Assert(propertyClassType == ClassType.Object);

currentValue = jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue, options);
obtainedValue = true;

if (currentValue != null)
{
Type runtimeType = currentValue.GetType();

// Ignore object() instances since they are handled as an empty object.
if (runtimeType != typeof(object))
{
propertyClassType = JsonClassInfo.GetClassType(runtimeType);
jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, runtimeType, options);
}
}
GetRuntimePropertyInfo(currentValue, state.Current.JsonClassInfo, ref jsonPropertyInfo, options);
}

state.Current.JsonPropertyInfo = jsonPropertyInfo;

if (propertyClassType == ClassType.Value)
if (jsonPropertyInfo.ClassType == ClassType.Value)
{
jsonPropertyInfo.Write(options, ref state.Current, ref writer);
state.Current.NextProperty();
return true;
}

// A property that returns an enumerator keeps the same stack frame.
if (propertyClassType == ClassType.Enumerable)
if (jsonPropertyInfo.ClassType == ClassType.Enumerable)
{
bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.ElementClassInfo, options, ref writer, ref state);
if (endOfEnumerable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,34 @@ namespace System.Text.Json.Serialization
{
public static partial class JsonSerializer
{
private static void GetRuntimeClassInfo(object value, ref JsonClassInfo jsonClassInfo, JsonSerializerOptions options)
{
if (value != null)
{
Type runtimeType = value.GetType();

// Nothing to do for typeof(object)
if (runtimeType != typeof(object))
{
jsonClassInfo = options.GetOrAddClass(runtimeType);
}
}
}

private static void GetRuntimePropertyInfo(object value, JsonClassInfo jsonClassInfo, ref JsonPropertyInfo jsonPropertyInfo, JsonSerializerOptions options)
{
if (value != null)
{
Type runtimeType = value.GetType();

// Nothing to do for typeof(object)
if (runtimeType != typeof(object))
{
jsonPropertyInfo = jsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, runtimeType, options);
}
}
}

private static void VerifyValueAndType(object value, Type type)
{
if (type == null)
Expand Down Expand Up @@ -88,13 +116,8 @@ private static void WriteCore(ArrayBufferWriter<byte> output, object value, Type
}

WriteStack state = default;
JsonClassInfo classInfo = options.GetOrAddClass(type);
state.Current.JsonClassInfo = classInfo;
state.Current.Initialize(type, options);
state.Current.CurrentValue = value;
if (classInfo.ClassType != ClassType.Object)
{
state.Current.JsonPropertyInfo = classInfo.GetPolicyProperty();
}

Write(ref writer, -1, options, ref state);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,9 @@ private static async Task WriteAsyncCore(object value, Type type, Stream utf8Jso
type = value.GetType();
}

JsonClassInfo classInfo = options.GetOrAddClass(type);
WriteStack state = default;
state.Current.JsonClassInfo = classInfo;
state.Current.Initialize(type, options);
state.Current.CurrentValue = value;
if (classInfo.ClassType != ClassType.Object)
{
state.Current.JsonPropertyInfo = classInfo.GetPolicyProperty();
}

bool isFinalBlock;

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.Diagnostics;

namespace System.Text.Json.Serialization
{
Expand Down Expand Up @@ -48,11 +49,17 @@ private static bool Write(
case ClassType.Enumerable:
finishedSerializing = WriteEnumerable(options, ref writer, ref state);
break;
case ClassType.Value:
finishedSerializing = WriteValue(options, ref writer, ref state.Current);
break;
case ClassType.Object:
finishedSerializing = WriteObject(options, ref writer, ref state);
break;
default:
finishedSerializing = WriteValue(options, ref writer, ref state.Current);
Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Unknown);

// Treat typeof(object) as an empty object.
finishedSerializing = WriteObject(options, ref writer, ref state);
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ internal struct ReadStackFrame
// The current JSON data for a property does not match a given POCO, so ignore the property (recursively for enumerables or object).
internal bool Drain;

internal void Initialize(Type type, JsonSerializerOptions options)
{
JsonClassInfo = options.GetOrAddClass(type);
if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable)
{
JsonPropertyInfo = JsonClassInfo.GetPolicyProperty();
}
}

internal void Reset()
{
ReturnValue = null;
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);
Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Unknown);
Current.PopStackOnEndObject = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ internal struct WriteStackFrame
internal bool PopStackOnEndArray;
internal bool PopStackOnEndObject;

internal void Initialize(Type type, JsonSerializerOptions options)
{
JsonClassInfo = options.GetOrAddClass(type);
if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable)
{
JsonPropertyInfo = JsonClassInfo.GetPolicyProperty();
}
}

internal void Reset()
{
CurrentValue = null;
Expand Down
Loading

0 comments on commit 35249a0

Please sign in to comment.