Skip to content

The 'new' modifier doesn't always work with [JsonIgnore] #59675

@steveharter

Description

@steveharter

For polymorphic scenarios, the new modifier does not always work with JsonIgnore. This affects both the reflection-based serializer and the source-gen serializer. See also #58985 (comment)

The semantics of the new modifier need to be documented and deterministic. For example, when [JsonIgnore] is applied to a derived type's property that has new and [JsonIgnore], does the base class property (which is a different property / has its own vtable slot) get serialized and deserialized?

Also verification is need when [JsonIgnore] and new are used along other attributes including [JsonPropertyOrder] on a derived type's property. The serializer should used the [JsonPropertyOrder] on the property that is actually serialized, and not the one that is ignored.

Test case failure

  string json = JsonSerializer.Serialize(new ConcreteDerivedClass());
  // Throws System.ArgumentException: 'An item with the same key has already been added. Key: Ignored_New_Property'  

  public class ConcreteDerivedClass : AbstractBaseClass
  {
      [JsonIgnore]
      public new int Ignored_New_Property { get; set; } = 1; // Also test with making this new slot method virtual
  }

  public abstract class AbstractBaseClass
  {
      [JsonIgnore]
      public int Ignored_New_Property { get; set; } = 2;
  }

Test case success 1 (serialize)

  string json = JsonSerializer.Serialize(new ConcreteDerivedClass());
  // Correctly returns "{\"Ignored_New_Property\":1}"
  
  public class ConcreteDerivedClass : AbstractBaseClass
  {
      public new int Ignored_New_Property { get; set; } = 1;
  }
  
  public abstract class AbstractBaseClass
  {
      [JsonIgnore]
      public int Ignored_New_Property { get; set; } = 2;
  }

Test case success 2 (serialize)

  string json = JsonSerializer.Serialize(new ConcreteDerivedClass());
  // Correctly returns "{\"Ignored_New_Property\":2}"
  
  public class ConcreteDerivedClass : AbstractBaseClass
  {
      [JsonIgnore]
      public new int Ignored_New_Property { get; set; } = 1;
  }
  
  public abstract class AbstractBaseClass
  {
      public int Ignored_New_Property { get; set; } = 2;
  }

Test case success 3 (deserialize)

  ConcreteDerivedClass obj = JsonSerializer.Deserialize<ConcreteDerivedClass>("{\"Ignored_New_Property\":42}");
  Debug.Assert(obj.Ignored_New_Property == 1);
  Debug.Assert(((AbstractBaseClass)obj).Ignored_New_Property == 42);

  public abstract class AbstractBaseClass
  {
      public int Ignored_New_Property { get; set; } = 2;
  }

  public class ConcreteDerivedClass : AbstractBaseClass
  {
      [JsonIgnore]
      public new virtual int Ignored_New_Property { get; set; } = 1;
  }

Test case success 4 (deserialize)

  ConcreteDerivedClass obj = JsonSerializer.Deserialize<ConcreteDerivedClass>("{\"Ignored_New_Property\":42}");
  Debug.Assert(obj.Ignored_New_Property == 42);
  Debug.Assert(((AbstractBaseClass)obj).Ignored_New_Property == 2);

  public abstract class AbstractBaseClass
  {
      [JsonIgnore]
      public int Ignored_New_Property { get; set; } = 2;
  }

  public class ConcreteDerivedClass : AbstractBaseClass
  {
      public new virtual int Ignored_New_Property { get; set; } = 1;
  }

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions