Skip to content

[API Proposal]: Add more extensive control over ignoring properties #55781

Closed

Description

@rogerfar

Background and motivation

If I have this config:

services.AddControllers()
		.AddJsonOptions(options =>
		{
			options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
			options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault;
			options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
		});

And I return the following:

[HttpGet]
[Route("Test")]
public ActionResult Test()
{
    return Ok(new
    {
        StringValue = "Prop",
        NullString = (string)null,
        DefaultNumber = 0,
        EnumValue = TestEnum.Val2,
        DefaultEnum = TestEnum.Val1
    });
}

The result is:

{
  "stringValue": "Prop",
  "enumValue": "Val2"
}

This can cause confusion because the value of DefaultEnum is omitted because it's default is, but because it's converted as a string this reduces clarity.

API Proposal

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Text.Json.Serialization
{
    /// <summary>
    /// When specified on <see cref="JsonSerializerOptions.DefaultIgnoreCondition"/>,
    /// determines when properties and fields across the type graph are ignored.
    /// When specified on <see cref="JsonIgnoreAttribute.Condition"/>, controls whether
    /// a property is ignored during serialization and deserialization. This option
    /// overrides the setting on <see cref="JsonSerializerOptions.DefaultIgnoreCondition"/>.
    /// </summary>
    public enum JsonIgnoreCondition
    {
        /// <summary>
        /// Property is never ignored during serialization or deserialization.
        /// </summary>
        Never = 0,
        /// <summary>
        /// Property is always ignored during serialization and deserialization.
        /// </summary>
        Always = 1,
        /// <summary>
        /// If the value is the default, the property is ignored during serialization.
        /// This is applied to both reference and value-type properties and fields.
        /// </summary>
        WhenWritingDefault = 2,
        /// <summary>
        /// If the value is <see langword="null"/>, the property is ignored during serialization.
        /// This is applied only to reference-type properties and fields.
        /// </summary>
        WhenWritingNull = 3,
        /// <summary>
        /// If the value is the default, the property is ignored during serialization.
        /// This is applied to both reference and value-type properties and fields.
		/// When using JsonStringEnumConverter enum properties will be included.
        /// </summary>
        WhenWritingDefaultExceptEnums = 4,
    }
}

API Usage

services.AddControllers()
		.AddJsonOptions(options =>
		{
			options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
			options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefaultExceptEnums;
			options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
		});

Risks

No response

Activity

  1. added
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Jul 15, 2021
  2. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Jul 15, 2021
  3. eiriktsarpalis commented on Jul 16, 2021

    @eiriktsarpalis
    Member

    Exposing a separate setting only to exempt JsonStringEnumConverter in particular seems a bit ad-hoc to me. Perhaps one alternative might be to expose a setting that disables ignored defaults when a custom converter is being used for a type.

    Related to #50294. It seems clear that WhenWritingDefault is too much of a blunt instrument when applied at the JsonSerializerOptions level and too noisy when applied at the individual property level.

    What we could do instead is expose a mechanism for omitting properties at the converter level, for example:

    public class JsonConverter<T>
    {
        public virtual bool IgnoreProperty(T propertyValue, JsonSerializerOptions options) => 
           // naive, partial implementation adopted for brevity
           options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingDefault &&
           EqualityComparer<T>.Default.Equals(propertyValue, default);
    }

    and thus completely opt out JsonStringEnumConverter by default:

    public class StringEnumConverter<TEnum>
    {
        public override bool IgnoreProperty(TEnum _, JsonSerializerOptions options) => false;
    }

    cc @layomia @NinoFloris

  4. added
    api-needs-workAPI needs work before it is approved, it is NOT ready for implementation
    and removed
    untriagedNew issue has not been triaged by the area owner
    on Jul 16, 2021
  5. added this to the 7.0.0 milestone on Jul 16, 2021
  6. NinoFloris commented on Jul 16, 2021

    @NinoFloris
    Contributor

    @eiriktsarpalis the proposal looks good to me, the only question to me would be whether the IgnoreProperty method is additive to or entirely overriding JsonSerializerOptions.JsonIgnoreCondition. Overriding is more powerful but I'd rather not come across too many 'constant false' converters that completely fail to respect any JsonIgnoreCondition. So it'd need some good guiding examples of how to do it right, either by looking up JsonIgnoreCondition in options or having specific converter options for it.

  7. rogerfar commented on Jul 16, 2021

    @rogerfar
    Author

    Would it be an idea to make DefaultIgnoreCondition not just an enum but an options object that accept multiple conditions? It will be a breaking change but not a very difficult one to fix.

  8. changed the title [-][API Proposal]: Exempt Enums when JsonIgnoreCondition.WhenWritingDefault and JsonStringEnumConverter[/-] [+][API Proposal]: Add more extensive control over ignoring properties[/+] on Oct 15, 2021
  9. eiriktsarpalis commented on Oct 16, 2021

    @eiriktsarpalis
    Member

    We might also want to consider #40395 for ignoring properties on the property level.

  10. nathan-alden-sr commented on Oct 24, 2021

    @nathan-alden-sr

    I would love this capability, especially as outlined in #55781 (comment). In my case, I am trying to serialize null or empty IList<T> references to null, but I want to honor the JsonIgnoreCondition, meaning I don't want the property serialized at all if the result of my algorithm would write null.

    public static void Main()
    {
        string json = JsonSerializer.Serialize(
            new Test(),
            new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, WriteIndented = true });
    
        Console.WriteLine(json);
    }
    
    public class Test
    {
        [JsonPropertyName("foos")]
        [JsonInclude]
        [JsonConverter(typeof(EmptyOrNullListConverter<string>))]
        public IList<string> Foos { get; } = new List<string>();
    }
    
    public class EmptyOrNullListConverter<T> : JsonConverter<IList<T>>
    {
        public override bool HandleNull => true;
    
        public override IList<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => default;
    
        public override void Write(Utf8JsonWriter writer, IList<T>? value, JsonSerializerOptions options) =>
            JsonSerializer.Serialize(writer, value?.Count > 0 ? value : null, options);
    }

    This gets me just about all the way there, but always writes "foos": null regardless of JsonIgnoreCondition.

  11. removed
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Nov 16, 2021
  12. scottdorman commented on Dec 3, 2021

    @scottdorman

    I'm in need of a similar solution to what @nathan-alden-sr mentioned. As there seem to be multiple issues relating to this scenario, there is a definite need for the functionality built-in to STJ. It's not currently possible with converters (or anything else that I can find) today. I commented on a related issue #36785 (comment) that has more specifics for the scenario I'm encountering.

  13. DavidErben commented on Dec 10, 2021

    @DavidErben

    @nathan-alden-sr I have posted a way to do this, maybe it works for you. You can check it our here:

    #36785 (comment)

  14. robertmclaws commented on Dec 22, 2021

    @robertmclaws

    See my solution here: #593 (comment)

  15. eiriktsarpalis commented on Jan 13, 2022

    @eiriktsarpalis
    Member

    I believe this could be addressed by #63686. We should make sure that this particular use case is covered and potential write a code sample demonstrating it.

    Regarding converter-level predicates (#55781 (comment)), I'm increasingly of the opinion that it should be addressed on the contract model instead. Closing in favor of #63686.

  16. ghost locked as resolved and limited conversation to collaborators on Feb 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Text.Json

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions