-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend default value handling in JsonSerializer #35649
Comments
Tagging subscribers to this area: @jozkee |
In the past, I've wanted to ignore enum JsonDefaultValuePolicy
{
SerializeAll,
IgnoreNull,
IgnoreDefault,
IgnoreNullOrDefault
} The property comment says default will be ignored on deserialization too. Am I understanding the behavior correctly here?: class MyClass
{
public int Foo { get; set; } = 123;
}
MyClass c = Deserialize<MyClass>("{'Foo': 0}");
Assert.Equal(123, c.Foo); |
That's a legit scenario. Rather than add a new enum, we could leverage the existing enum JsonIgnoreCondition
{
Always (per-property default when used as `[JsonIgnore] condition`; invalid as global option),
WhenNull,
+ WhenValueDefault,
+ WhenDefault,
Never (global default)
} Alternatively, instead of making FWIW, with the current API proposal you could prevent value-type namespace System.Text.Json
{
public partial class JsonSerializerOptions
{
/// <summary>
/// Determines whether null values are ignored during serialization and deserialization.
/// The default value is false.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if this property is set after serialization or deserialization has occurred.
/// </exception>
[Obsolete("This property is obsolete. Use IgnoreDefaultValues instead.", error: false)]
public bool IgnoreNullValues { get; set; }
/// <summary>
/// Determines when property values are ignored during serialization and deserialization.
/// The default value is <see cref="JsonIgnoreCondition.Never" />.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if this property is set after serialization or deserialization has occurred.
/// </exception>
public JsonIgnoreCondition IgnoreCondition { get; set; } = JsonIgnoreCondition.Never;
}
}
Yes, that's the behavior I'm proposing. Wanting to ignore value-type defaults on deserialization is probably an edge case, but it may have some perf benefits if a bunch of 0s and FWI Newtonsoft ignores default values on deserialization when the option is active. Differing here might come as a surprise to some users: using System;
using Newtonsoft.Json;
public class Program
{
public static void Main()
{
var settings = new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Ignore,
};
string json = @"{""MyInt"": 0}";
var obj = JsonConvert.DeserializeObject<MyClass>(json, settings);
Console.WriteLine(obj.MyInt); // 123
}
public class MyClass
{
public int MyInt { get; set; } = 123;
}
} |
namespace System.Text.Json
{
public partial class JsonSerializerOptions
{
[Obsolete("This property is obsolete. Use IgnoreDefaultValues instead.", error: false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IgnoreNullValues { get; set; }
public bool SerializeDefaultValues { get; set; } = true;
}
} |
Following feedback, @steveharter and I spoke with @JamesNK about whether providing an option to ignore default values when deserializing should be prioritized. James has not received much feedback about this, and doesn't think that it is a major scenario. Here's the updated proposal: Modify option to specify when to ignore propertiesnamespace System.Text.Json.Serialization
{
/// <summary>
/// Controls how the <see cref="JsonIgnoreAttribute"/> ignores properties on serialization and deserialization.
/// </summary>
public enum JsonIgnoreCondition
{
/// <summary>
/// Property will always be serialized and deserialized.
/// </summary>
Never = 0,
/// <summary>
/// Property will always be ignored when serializing and deserializing.
/// </summary>
Always = 1,
/// <summary>
/// Property will be ignored when serializing if it is the default value.
/// </summary>
WhenWritingDefaultValues = 2
}
} This enum was added in .NET 5 preview 3. In the future, we could add The current values are Today, Provide global option to ignore default values when serializingnamespace System.Text.Json
{
public partial class JsonSerializerOptions
{
/// <summary>
/// Specifies a condition to determine when properties with default values are ignored during serialization or deserialization.
/// The default value is <see cref="JsonIgnoreCondition.Never" />.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if this property is set after serialization or deserialization has occurred,
/// or if this property is set to <see cref="JsonIgnoreCondition.Always"/>
/// </exception>
public JsonIgnoreCondition DefaultIgnoreCondition { get; set; } = JsonIgnoreCondition.Never;
}
} This approach gives us flexibility to allow ignoring default values when deserializing in the future (by adding a new enum value to Alternate approach (click to view)namespace System.Text.Json
{
public partial class JsonSerializerOptions
{
/// <summary>
/// Determines whether default values are ignored during serialization.
/// The default value is false.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if this property is set after serialization or deserialization has occurred.
/// </exception>
public bool IgnoreDefaultValues { get; set; }
}
} We prefer for this to be named in a way that the default value is false. This is to be consistent with the other options on JsonSerializerOptions. If desired in the future, more properties would need to be added to JsonSerializerOptions to allow ignoring default values when deserializing. Make
|
namespace System.Text.Json.Serialization
{
public enum JsonIgnoreCondition
{
Never = 0,
Always = 1,
WhenWritingDefault = 2
}
}
namespace System.Text.Json
{
public partial class JsonSerializerOptions
{
public JsonIgnoreCondition DefaultIgnoreCondition { get; set; } = JsonIgnoreCondition.Never;
}
} |
Closing this composite issue as there are tracking issues for the sub-tasks:
|
Priority scenarios (for .NET 5)
Ignoring default values
See #779 for more info.
We currently have global and per-property options to ignore null when serializing and deserializing reference types. This has peformance benefits, as property getters and setters are called less often. Also, less JSON can be emitted when serializing, meaning that less data can be sent across the wire (if applicable), and there is less data to process when deserializing again.
We should add options to ignore
default
values for value types.Passing null to custom converters
API approved in #34439. Click for more info.
Null values are not passed to converters for reference types. Users may wish to handle (de)serialization of types and properties with custom converters. This should include null values and JSON tokens, if so desired.
For example, in the Cosmos v3 database, when deserializing a CRS class (Coordinate Reference System) instance and the input JSON is null, a new UnspecifiedCrs instance is set instead.
A workaround here is to set the default value during instantiation of the type, and ignore
null
values on deserialization. This may have a noticeable perf impact if multiple properties are set to custom defaults during object instantiation, only for several of them to be set again when deserializing.API proposal
See updated proposal following review feedback - #35649 (comment).
Initial proposal (click to view)
Option to ignore default values globally
Add
IgnoreDefaultValues
default values option that can be applied to both reference and value types.It would be invalid for a caller to set both
IgnoreNullValues
&IgnoreDefaultValues
.What about people who want to ignore null values, but include value-type default values?
They can use
[JsonIgnore(Condition = Never)]
on each property to include. See #35649 (comment) for an alternate approach that has more granularity.Option to ignore default values per property
Rename
JsonIgnoreCondition.WhenNull
toJsonIgnoreCondition.WhenDefault
.This is a breaking change between preview 4 & 5.
API usage
Ignoring default values
Ignoring default values globally but including some properties:
Hand-picking properties to ignore on serialization when default
Additional features (5.0 backlog/future)
Ability to specify a custom default value
Users may want to specify a custom default value to be used instead of the type default.
Replacing type defaults or missing values with custom defaults when serializing and deserializing
EDIT: After discussing with @JamesNK, it was discovered that this feature, which is equivalent to applying
DefaultValueHandling.Populate
was not created in Newtonsoft.Json to solve any particular customer scenario, and is likely not useful for many customers. Thus, API for this scenario will not be prioritized.This feature allows a custom default value to be used instead of the property's actual value:
Altogether, these features give
JsonSerializer
parity withNewtonsoft.Json
's serializer around default value handling, excluding programatic configuration capabilities enabled with exposingJsonClassInfo
,JsonPropertyInfo
& friends; resulting in a model similar to using a custom [contract resolver(https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_DefaultContractResolver.htm). In this model, we can expose aPredicate<T>
based model for determining whether to ignore or replace the value of a property, similar toNewtonsoft
'sShouldSerialize
andShouldDeserialize
properties.API proposals for these features (click to expand).
API proposals
Option for users to specify a custom default value for a type, property, or field
The API is designed such that reference-type properties do not have to share the same instance as the default value. The default value provided overrides the type default.
Notes
DefaultValueProviderAttribute
is provided to accomodate callers whoT
must be the same as any custom converter specified for the type; otherwise,typeof(T)
must be equal to the type.DefaultValueProvider
base class is useful to provide future functionalitybool IsCompatibleWith(Type)
method that will be used if we add the ability to specify the default value for a type at runtime, similar toJsonConverter.CanConvert
DefaultValueProvider
type, then the return type ofCreateProvider
above will have to betypeof(object?)
.Optional addition: extend
JsonConverter<T>
If a custom converter is provided for the type, it can opt into providing a default value for the type. This avoids having to specify a converter and a default value provider.
Option to globally specify when to replace values with a custom default
It is invalid for a custom default value not to be specified when
PopulateCondition
is set to a value other thanJsonPopulateCondition.Never
.It is invalid for a custom default value not to be specified when this attribute is used.
Option to specify a condition for a property or field to be set to a custom default
Interaction with converters handling
null
on serialization and deserializationThe behavior when null is encountered when (de)serializing a property is as follows:
If
HandleNull
istrue
:If
JsonPopulate
is active on the property:Read
/Write
method will not be called, and the specified default will be set.If
JsonPopulate
is not active:Read
/Write
method will be called with the null value as an input.If
HandleNull
isfalse
:If
JsonPopulate
is active on the property:If
JsonPopulate
is not active on the property:If
IgnoreDefaultValues
is active, thennull
is not passed to the converter. This can be overriden on a property or field usingJsonIgnoreAttribute
with theCondition
set toNever
.Using the
ShouldSerializeXxx
patternUsing the
ShouldSerializeXxx
pattern to indicate whether a property should be serialized (in the context of ignoring default values) was considered but not pursed for the following reasons:ShouldDeserializeXxx
as wellShould(De)Serialize
models mentioned above).Examples
The "replace" operation occurs before the "ignore" operation.
Given a type
MyClass
and and a default value provider for the string type:Populating with a custom default when there's no JSON value for the property, or the property is the CLR default.
Given a
MyClass
class:The text was updated successfully, but these errors were encountered: