-1

Consider you have:

class ContentRef {
  SomeProperty
}

class Content : ContentRef {
   SomeOtherProperty
}

class C  {
   ContentRef someProperty;
   List<ContentRef> someList;
}

var someObject = new C { someProperty = new Content(), someList = new List<ContentRef>({new Content()})};

By default both Newtonsoft and System.Text.Json serialize all of the properties of Content in someObject. But clearly, the definition for C says that both are of type A. I want to make sure that in both the case of the direct property A and List that the only thing that gets serialized is A's properties even if you assign B into it.

This happens all of the time in document databases when you have a Ref class for a property that you're setting and you set it to the full class that inherits from the ref class for the rest of the data. You only want the document database to store the ref class's data, not all of the full class because when it's deserialized you only care about the Ref class as defined.

I've got this:

public class StrictTypeContractResolver : DefaultContractResolver
{
    private readonly FieldInfo _IsSealedField = typeof(JsonContract).GetField("IsSealed", BindingFlags.Instance | BindingFlags.NonPublic)!;

    public override JsonContract ResolveContract(Type type)
    {
        var resolveContract = base.ResolveContract(type);
        _IsSealedField.SetValue(resolveContract, true);
        return resolveContract;
    }
}

Which works in the direct property case and only serializes the actual defined type. However, I can't figure out how to get it to work on the List type (either a hashset, IList, or IEnumerable). It serializes the entire thing instead of just the List like it should.

James Hancock
  • 3,348
  • 5
  • 34
  • 59
  • There are examples of contract resolvers that ignore base type members [here](https://stackoverflow.com/a/50976009/3744182) and [here](https://stackoverflow.com/a/35634915/3744182). You might check to see whether those work for you, and if not, might you please share a [mcve]? Your `ContentRef` example seems to be pseudocode so it's unclear to me where you are having problems. – dbc Mar 26 '21 at 21:29
  • Also, why tagged both [tag:system.text.json] and [tag:json.net]? `System.Text.Json` [doesn't even have a publicly facing contract resolver](https://stackoverflow.com/q/58926112/3744182) so an answer there will be completely different. – dbc Mar 26 '21 at 21:31
  • It isn’t the base type I want to ignore. I want to ignore the inherited type properties when the property or list element type is a base type and it should be deserialized to the defined type of the property not whatever was passed in. It’s as simple as setting property that is defined as ContentRef with a Content will cause it to be serialized with all of Content not ContentRef and it should ignore the rest and only serialism ContentRef. Worse, it also does this with list elements even when the type is clearly defined. So I’m trying to undo this nonsensical behavior. – James Hancock Mar 26 '21 at 22:49
  • Ps: both json.net and System.Text.Json exhibit the same nonsensical behavior and ignore your type definition and serialize the child type instead of the base type as defined. – James Hancock Mar 26 '21 at 22:51
  • I would argue that automatically serializing the properties of derived types is not nonsensical at all; in fact it is preferred. Say you have a class Canvas that contains a list of Shapes. The base Shape has a Name property and a Position on the Canvas. But each derivative shape has specific properties that define it-- for example a Circle has a Center and Radius whereas a Polygon has a series of Vertices. If I were to serialize the Canvas, I would absolutely want the details of the shapes to be serialized as well or else I wouldn't be able to reconstruct the full Canvas from the JSON! – Brian Rogers Mar 26 '21 at 23:43
  • Your example of polymorphism is the exception not the rule. It’s very rare to have polymorphic data and very common to have the defined type. An attribute can easily set the exception. It’s hard to get devs to remember on the norm, especially when it’s no obvious unless you inspect that you’re wasting all of that space because there’s no error like there would be in the polymorphic case if you didn’t set the attribute. – James Hancock Mar 29 '21 at 23:24

1 Answers1

0

https://dotnetfiddle.net/CCbOFf

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using static Newtonsoft.Json.JsonConvert;

class Ref { public int a {get;set;} public string b {get;set;} public DateTime c {get;set;} }
class DerivedRef : Ref { public bool d {get;set;} public long e {get;set;} }

class Ref2 { public int a {get;set;} public string b {get;set;} public DateTime c {get;set;} }
class DerivedRef2 : Ref2 { public bool d {get;set;} public long e {get;set;} }

public class StrictTypeContractResolver<T>: DefaultContractResolver
{
    
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        if(type.IsSubclassOf(typeof(T)))
        {           
            return base.CreateProperties(typeof(T), memberSerialization);
        }
        
        return base.CreateProperties(type, memberSerialization);
    }
    
}

public class Program
{
    public static void Main()
    {       
        var list = new Ref[]{
            new Ref{a=1,b="2",c=DateTime.Now}
            ,new DerivedRef{a=1,b="2",c=DateTime.Now,d=true,e=999}
        };

        Console.WriteLine(PerformSerialization(list));
            
        var list2 = new Ref2[]{
            new Ref2{a=1,b="2",c=DateTime.Now}
            ,new DerivedRef2{a=1,b="2",c=DateTime.Now,d=true,e=999}
        };

        Console.WriteLine(PerformSerialization(list2));
    }
    
    public static string PerformSerialization<T>(IList<T> list)
    {
        var settings = new JsonSerializerSettings { ContractResolver = new StrictTypeContractResolver<T>(), Formatting = Formatting.Indented  };
        return SerializeObject(list, settings);
    }
    
    
}
Rex Henderson
  • 412
  • 2
  • 7
  • The problem is that then I have to maintain a list of every one in the method. instead of it just working intelligently. This has to be possible without having to resort to that. – James Hancock Mar 26 '21 at 23:23
  • So you're saying it will always be a DerivedRef : Ref relationship and you want it to just take Ref, whatever the Ref type may be? – Rex Henderson Mar 26 '21 at 23:26
  • No it’s that I have dozens of these examples and adding more as the framework grows and having to remember to maintain that is a recipe for bugs and I don’t want the full object persisted in the document store, just the reference and virtually always you’ll assign the full object to the base type because you’ll have the whole thing loaded. And having to remember to force castor or map it is more bugs waiting to happen for what should be obvious: persist the defined type exactly and ignore any inherited stuff. – James Hancock Mar 26 '21 at 23:29
  • Sounds like you want a generic T solution. – Rex Henderson Mar 26 '21 at 23:34
  • Generic T would still require every one of them be defined. I want the default behavior to serialize exactly as defined and allow exceptions for polymorphism with an attribute that is seton that rare case. I’m working on a custom converter that does it but ravendb ignores it so working around that. – James Hancock Mar 29 '21 at 23:22