Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

JsonSerializerOptions API update and ignore property features #36776

Merged
merged 2 commits into from
Apr 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/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/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/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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove unnecessary brackets.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a slight preference and think the brackets make it more obvious that this is a bool... Willing to change it however if I'm the only one :)


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