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 1 commit
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
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
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,20 @@ 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;

protected bool HasGetter { get; set; }
protected bool HasSetter { get; set; }
protected bool UseGetter { get; private set; }
protected bool UseSetter { get; private set; }
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> Name => _name;
Expand All @@ -45,6 +51,7 @@ internal JsonPropertyInfo(
ClassType = JsonClassInfo.GetClassType(runtimePropertyType);
if (elementType != null)
{
Debug.Assert(ClassType == ClassType.Enumerable);
ElementClassInfo = options.GetOrAddClass(elementType);
}

Expand All @@ -68,26 +75,73 @@ 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);
Copy link
Member

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.
//}
}
}

bool hasIgnoreAttribute = (GetAttribute<JsonIgnoreAttribute>() != null);
//else if (HasSetter)
//{
// // todo: Special case where there is no getter but a setter (and an EnumerableConverter)
//}

UseGetter = HasGetter && !hasIgnoreAttribute && (HasSetter || !options.IgnoreReadOnlyProperties);
UseSetter = HasSetter && !hasIgnoreAttribute;

IgnoreNullValues = options.IgnoreNullValues;
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);
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 (UseSetter)
else if (ShouldDeserialize)
{
if (ValueConverter != null)
{
Expand All @@ -48,10 +48,10 @@ internal override void Read(JsonTokenType tokenType, JsonSerializerOptions optio
}
else
{
if (value != null || !IgnoreNullValues)
{
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 (UseGetter)
else if (ShouldSerialize)
{
TRuntimeProperty value;
if (_isPropertyPolicy)
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 (UseSetter)
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 (UseGetter)
else if (ShouldSerialize)
{
TProperty? value;
if (_isPropertyPolicy)
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 @@ -11,8 +11,7 @@ private static object ReadCore(
JsonSerializerOptions options,
ref Utf8JsonReader reader)
{
if (options == null)
options = JsonSerializerOptions.s_defaultOptions;
options ??= JsonSerializerOptions.s_defaultOptions;

ReadStack state = default;
state.Current.Initialize(returnType, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ public static object Parse(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSer

private static object ParseCore(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options)
{
if (options == null)
options = JsonSerializerOptions.s_defaultOptions;
options ??= JsonSerializerOptions.s_defaultOptions;

var readerState = new JsonReaderState(options.GetReaderOptions());
var reader = new Utf8JsonReader(utf8Json, isFinalBlock: true, readerState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ private static bool HandleEnumerable(
Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable);

JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
if (!jsonPropertyInfo.ShouldSerialize)
{
// Ignore writing this property.
return true;
}

if (state.Current.Enumerator == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public JsonSerializerOptions() { }

/// <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>
/// <exception cref="InvalidOperationException">
/// Thrown if this property is set after serialization or deserialization has occurred.
Expand Down Expand Up @@ -142,8 +142,8 @@ public int MaxDepth
}

/// <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.
/// Defines how the comments are handled during deserialization.
/// By default <exception cref="JsonReaderException"/> is thrown if a comment is encountered.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if this property is set after serialization or deserialization has occurred.
Expand All @@ -162,7 +162,7 @@ public JsonCommentHandling ReadCommentHandling
}

/// <summary>
/// Defines whether the <see cref="Utf8JsonWriter"/> should pretty print the JSON which includes:
/// Defines whether JSON should pretty print which includes:
Copy link
Member

Choose a reason for hiding this comment

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

JSON should be pretty printed

/// indenting nested JSON tokens, adding new lines, and adding white space between property names and values.
/// By default, the JSON is written without any extra white space.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions src/System.Text.Json/tests/Serialization/Array.ReadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@
// 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.Collections.Generic;
using Xunit;

namespace System.Text.Json.Serialization.Tests
{
public static partial class ArrayTests
{
[Fact]
public static void ReadEmpty()
{
SimpleTestClass[] arr = JsonSerializer.Parse<SimpleTestClass[]>("[]");
Assert.Equal(0, arr.Length);

List<SimpleTestClass> list = JsonSerializer.Parse<List<SimpleTestClass>>("[]");
Assert.Equal(0, list.Count);
}

[Fact]
public static void ReadClassWithStringArray()
{
Expand Down
11 changes: 11 additions & 0 deletions src/System.Text.Json/tests/Serialization/Array.WriteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@
// 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.Collections.Generic;
using Xunit;

namespace System.Text.Json.Serialization.Tests
{
public static partial class ArrayTests
{
[Fact]
public static void WriteEmpty()
{
string json = JsonSerializer.ToString(new SimpleTestClass[] { });
Assert.Equal("[]", json);

json = JsonSerializer.ToString(new List<SimpleTestClass>());
Assert.Equal("[]", json);
}

[Fact]
public static void WriteClassWithStringArray()
{
Expand Down
8 changes: 4 additions & 4 deletions src/System.Text.Json/tests/Serialization/Null.ReadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ public static void RootObjectIsNull()
}

[Fact]
public static void DefaultReadValue()
public static void DefaultIgnoreNullValuesOnRead()
{
TestClassWithNullButInitialized obj = JsonSerializer.Parse<TestClassWithNullButInitialized>(TestClassWithNullButInitialized.s_json);
TestClassWithInitializedProperties obj = JsonSerializer.Parse<TestClassWithInitializedProperties>(TestClassWithInitializedProperties.s_null_json);
Assert.Equal(null, obj.MyString);
Assert.Equal(null, obj.MyInt);
}

[Fact]
public static void IgnoreNullValuesOnOptionsWithRead()
public static void EnableIgnoreNullValuesOnRead()
{
var options = new JsonSerializerOptions();
options.IgnoreNullValues = true;

TestClassWithNullButInitialized obj = JsonSerializer.Parse<TestClassWithNullButInitialized>(TestClassWithNullButInitialized.s_json, options);
TestClassWithInitializedProperties obj = JsonSerializer.Parse<TestClassWithInitializedProperties>(TestClassWithInitializedProperties.s_null_json, options);
Assert.Equal("Hello", obj.MyString);
Assert.Equal(1, obj.MyInt);
}
Expand Down
Loading