Skip to content

Commit

Permalink
JsonSerializerOptions API update and ignore property features (dotnet…
Browse files Browse the repository at this point in the history
  • Loading branch information
steveharter authored Apr 13, 2019
1 parent c5de21a commit d5e7e40
Show file tree
Hide file tree
Showing 34 changed files with 626 additions and 134 deletions.
21 changes: 16 additions & 5 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ public override void GetObjectData(System.Runtime.Serialization.SerializationInf
public partial struct JsonReaderOptions
{
private int _dummyPrimitive;
public bool AllowTrailingCommas { get { throw null; } set { } }
public System.Text.Json.JsonCommentHandling CommentHandling { get { throw null; } set { } }
public int MaxDepth { get { throw null; } set { } }
public bool AllowTrailingCommas { get { throw null; } set { } }
}
public partial struct JsonReaderState
{
Expand Down Expand Up @@ -311,6 +311,15 @@ public void WriteStringValue(string value, bool escape = true) { }
}
namespace System.Text.Json.Serialization
{
public abstract partial class JsonAttribute : System.Attribute
{
protected JsonAttribute() { }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
public sealed partial class JsonIgnoreAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonIgnoreAttribute() { }
}
public static partial class JsonSerializer
{
public static object Parse(System.ReadOnlySpan<byte> utf8Json, System.Type returnType, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
Expand All @@ -329,10 +338,12 @@ public static partial class JsonSerializer
public sealed partial class JsonSerializerOptions
{
public JsonSerializerOptions() { }
public bool AllowTrailingCommas { get { throw null; } set { } }
public int DefaultBufferSize { get { throw null; } set { } }
public bool IgnoreNullPropertyValueOnRead { get { throw null; } set { } }
public bool IgnoreNullPropertyValueOnWrite { get { throw null; } set { } }
public System.Text.Json.JsonReaderOptions ReaderOptions { get { throw null; } set { } }
public System.Text.Json.JsonWriterOptions WriterOptions { get { throw null; } set { } }
public bool IgnoreNullValues { get { throw null; } set { } }
public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
public int MaxDepth { get { throw null; } set { } }
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
public bool WriteIndented { get { throw null; } set { } }
}
}
3 changes: 3 additions & 0 deletions src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,7 @@
<data name="TrailingCommaNotAllowedBeforeObjectEnd" xml:space="preserve">
<value>The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options.</value>
</data>
<data name="SerializerOptionsImmutable" xml:space="preserve">
<value>Serializer options cannot be changed once serialization or deserialization has occurred.</value>
</data>
</root>
3 changes: 3 additions & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,17 @@
<Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt16.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt32.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt64.cs" />
<Compile Include="System\Text\Json\Serialization\JsonAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
<Compile Include="System\Text\Json\Serialization\JsonEnumerableConverter.cs" />
<Compile Include="System\Text\Json\Serialization\JsonIgnoreAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfoCommon.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfoNotNullable.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfoNullable.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleArray.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Stream.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleValue.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleObject.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public struct JsonReaderOptions

/// <summary>
/// Defines how the <see cref="Utf8JsonReader"/> should handle comments when reading through the JSON.
/// By default the reader will throw a <exception cref="JsonReaderException"/> if it encounters a comment.
/// By default <exception cref="JsonReaderException"/> is thrown if a comment is encountered.
/// </summary>
public JsonCommentHandling CommentHandling { get; set; }

Expand All @@ -37,8 +37,8 @@ public int MaxDepth

/// <summary>
/// Defines whether an extra comma at the end of a list of JSON values in an object or array
/// are allowed (and ignored) within the JSON payload being read.
/// By default, it's set to false, and the reader will throw a <exception cref="JsonReaderException"/> if it encounters a trailing comma.
/// is allowed (and ignored) within the JSON payload being read.
/// By default, it's set to false, and <exception cref="JsonReaderException"/> is thrown if a trailing comma is encountered.
/// </summary>
public bool AllowTrailingCommas { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Text.Json.Serialization
{
/// <summary>
/// The base class of serialization attributes.
/// </summary>
public abstract class JsonAttribute : Attribute { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Text.Json.Serialization
{
/// <summary>
/// Prevents a property from being serialized or deserialized.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class JsonIgnoreAttribute : JsonAttribute
{
public JsonIgnoreAttribute() { }
}
}
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.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json.Serialization.Converters;
using System.Text.Json.Serialization.Policies;
Expand All @@ -11,15 +12,22 @@ namespace System.Text.Json.Serialization
{
internal abstract class JsonPropertyInfo
{
// Cache the array and enumerable converters so they don't get created for every enumerable property.
private static readonly JsonEnumerableConverter s_jsonArrayConverter = new DefaultArrayConverter();
private static readonly JsonEnumerableConverter s_jsonEnumerableConverter = new DefaultEnumerableConverter();

internal ClassType ClassType;

internal byte[] _name = default;
internal byte[] _escapedName = default;

internal bool HasGetter { get; set; }
internal bool HasSetter { get; set; }
internal bool ShouldSerialize { get; private set; }
internal bool ShouldDeserialize { get; private set; }

internal bool IgnoreNullValues { get; private set; }

public ReadOnlySpan<byte> EscapedName => _escapedName;
public ReadOnlySpan<byte> Name => _name;

// todo: to minimize hashtable lookups, cache JsonClassInfo:
Expand All @@ -43,6 +51,7 @@ internal JsonPropertyInfo(
ClassType = JsonClassInfo.GetClassType(runtimePropertyType);
if (elementType != null)
{
Debug.Assert(ClassType == ClassType.Enumerable);
ElementClassInfo = options.GetOrAddClass(elementType);
}

Expand All @@ -66,31 +75,80 @@ internal JsonPropertyInfo(

internal virtual void GetPolicies(JsonSerializerOptions options)
{
if (RuntimePropertyType.IsArray)
DetermineSerializationCapabilities(options);
IgnoreNullValues = options.IgnoreNullValues;
}

private void DetermineSerializationCapabilities(JsonSerializerOptions options)
{
bool hasIgnoreAttribute = (GetAttribute<JsonIgnoreAttribute>() != null);

if (hasIgnoreAttribute)
{
EnumerableConverter = new DefaultArrayConverter();
// We don't serialize or deserialize.
return;
}
else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType))

if (ClassType != ClassType.Enumerable)
{
Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType);
// We serialize if there is a getter + no [Ignore] attribute + not ignoring readonly properties.
ShouldSerialize = HasGetter && (HasSetter || !options.IgnoreReadOnlyProperties);

if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType)))
// We deserialize if there is a setter + no [Ignore] attribute.
ShouldDeserialize = HasSetter;
}
else
{
if (HasGetter)
{
EnumerableConverter = new DefaultEnumerableConverter();
if (HasSetter)
{
ShouldDeserialize = true;
}
else if (RuntimePropertyType.IsAssignableFrom(typeof(IList)))
{
ShouldDeserialize = true;
}
//else
//{
// // todo: future feature that allows non-List types (e.g. from System.Collections.Immutable) to have converters.
//}
}
//else if (HasSetter)
//{
// // todo: Special case where there is no getter but a setter (and an EnumerableConverter)
//}

if (ShouldDeserialize)
{
ShouldSerialize = HasGetter;

if (RuntimePropertyType.IsArray)
{
EnumerableConverter = s_jsonArrayConverter;
}
else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType))
{
Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType);

if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType)))
{
EnumerableConverter = s_jsonEnumerableConverter;
}
}
}
else
{
ShouldSerialize = HasGetter && !options.IgnoreReadOnlyProperties;
}
}
}

internal abstract object GetValueAsObject(object obj, JsonSerializerOptions options);

internal bool IgnoreNullPropertyValueOnRead(JsonSerializerOptions options)
{
return options.IgnoreNullPropertyValueOnRead;
}

internal bool IgnoreNullPropertyValueOnWrite(JsonSerializerOptions options)
internal TAttribute GetAttribute<TAttribute>() where TAttribute : Attribute
{
return options.IgnoreNullPropertyValueOnWrite;
return (TAttribute)PropertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false);
}

internal abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ internal override void SetValueAsObject(object obj, object value, JsonSerializer
Debug.Assert(Set != null);
TDeclaredProperty typedValue = (TDeclaredProperty)value;

if (typedValue != null || !IgnoreNullPropertyValueOnWrite(options))
if (typedValue != null || !IgnoreNullValues)
{
Set((TClass)obj, (TDeclaredProperty)value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal override void Read(JsonTokenType tokenType, JsonSerializerOptions optio
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
}
else if (HasSetter)
else if (ShouldDeserialize)
{
if (ValueConverter != null)
{
Expand All @@ -48,10 +48,10 @@ internal override void Read(JsonTokenType tokenType, JsonSerializerOptions optio
}
else
{
if (value != null || !IgnoreNullPropertyValueOnRead(options))
{
Set((TClass)state.Current.ReturnValue, value);
}
// Null values were already handled.
Debug.Assert(value != null);

Set((TClass)state.Current.ReturnValue, value);
}

return;
Expand Down Expand Up @@ -85,7 +85,7 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.WriteEnumerable(options, ref current, ref writer);
}
else if (HasGetter)
else if (ShouldSerialize)
{
TRuntimeProperty value;
if (_isPropertyPolicy)
Expand All @@ -103,7 +103,7 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame
{
writer.WriteNullValue();
}
else if (!IgnoreNullPropertyValueOnWrite(options))
else if (!IgnoreNullValues)
{
writer.WriteNull(_escapedName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal override void Read(JsonTokenType tokenType, JsonSerializerOptions optio
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
}
else if (HasSetter)
else if (ShouldDeserialize)
{
if (ValueConverter != null)
{
Expand Down Expand Up @@ -82,7 +82,7 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.WriteEnumerable(options, ref current, ref writer);
}
else if (HasGetter)
else if (ShouldSerialize)
{
TProperty? value;
if (_isPropertyPolicy)
Expand All @@ -100,7 +100,7 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame
{
writer.WriteNullValue();
}
else if (!IgnoreNullPropertyValueOnWrite(options))
else if (!IgnoreNullValues)
{
writer.WriteNull(_escapedName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ private static void HandleStartArray(
ref Utf8JsonReader reader,
ref ReadStack state)
{
if (state.Current.Skip())
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;

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

JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
if (jsonPropertyInfo == null || state.Current.JsonClassInfo.ClassType == ClassType.Unknown)
{
jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options);
Expand Down Expand Up @@ -53,8 +55,10 @@ private static void HandleStartArray(
state.Current.EnumerableCreated = true;
}

jsonPropertyInfo = state.Current.JsonPropertyInfo;

// If current property is already set (from a constructor, for example) leave as-is
if (state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null)
if (jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null)
{
// Create the enumerable.
object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, J
return true;
}

if (!propertyInfo.IgnoreNullPropertyValueOnRead(options))
if (!propertyInfo.IgnoreNullValues)
{
state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null, options);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Text.Json.Serialization
{
public static partial class JsonSerializer
{
private static object ReadCore(
Type returnType,
JsonSerializerOptions options,
ref Utf8JsonReader reader)
{
options ??= JsonSerializerOptions.s_defaultOptions;

ReadStack state = default;
state.Current.Initialize(returnType, options);

ReadCore(options, ref reader, ref state);

return state.Current.ReturnValue;
}
}
}
Loading

0 comments on commit d5e7e40

Please sign in to comment.