36

Given a data model:

[DataContract]
public class Parent
{
    [DataMember]
    public IEnumerable<ChildId> Children { get; set; }
}

[DataContract]
public class ChildId
{
    [DataMember]
    public string Id { get; set; }
}

[DataContract]
public class ChildDetail : ChildId
{
    [DataMember]
    public string Name { get; set; }
}

For implementation convenience reasons, there are times when the ChildId objects on the Parent are in fact ChildDetail objects. When I use JSON.net to serialize the Parent, they are written out with all of the ChildDetail properties.

Is there any way to instruct JSON.net (or any other JSON serializer, I'm not far enough into the project to be committed to one) to ignore derived class properties when serializing as a base class?

EDIT: It is important that when I serialize the derived class directly that I'm able to produce all the properties. I only want to inhibit the polymorphism in the Parent object.

Christopher Currie
  • 3,025
  • 1
  • 29
  • 40

6 Answers6

32

I use a custom Contract Resolver to limit which of my properties to serialize. This might point you in the right direction.

e.g.

/// <summary>
/// json.net serializes ALL properties of a class by default
/// this class will tell json.net to only serialize properties if they MATCH 
/// the list of valid columns passed through the querystring to criteria object
/// </summary>
public class CriteriaContractResolver<T> : DefaultContractResolver
{
    List<string> _properties;

    public CriteriaContractResolver(List<string> properties)
    {
        _properties = properties
    }

    protected override IList<JsonProperty> CreateProperties(
        JsonObjectContract contract)
    {
        IList<JsonProperty> filtered = new List<JsonProperty>();

        foreach (JsonProperty p in base.CreateProperties(contract))
            if(_properties.Contains(p.PropertyName)) 
                filtered.Add(p);

        return filtered;
    }
}

In the override IList function, you could use reflection to populate the list with only the parent properties perhaps.

Contract resolver is applied to your json.net serializer. This example is from an asp.net mvc app.

JsonNetResult result = new JsonNetResult();
result.Formatting = Formatting.Indented;
result.SerializerSettings.ContractResolver = 
    new CriteriaContractResolver<T>(Criteria);
Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
Jason Watts
  • 3,800
  • 29
  • 33
  • 1
    This is exactly what I was looking for. A partial response based on fields passed in the URI. Thanks! [I wonder why this isn't a built in Contract Resolver] – David Kassa Apr 28 '12 at 18:10
  • Do you have an example of the IRestCriteria class? Where does that come from? – Micah Jun 12 '12 at 12:54
  • 1
    Micah - I should not have just cut and pasted probably. I have since changed this class to take in a List of property names. – Jason Watts Jun 12 '12 at 14:15
  • 1
    I dont think you should call base.CreateProperties, because you're not calling the new override method you've writted (with only accepts only one parameter), so you had to pass 2 parameters instead of just contract. – Gui Aug 13 '12 at 13:37
  • This does what I need - except how can I set this behaviour just for certain class types, or have different sets of properties included for different types? – Rn222 Sep 19 '12 at 19:28
  • This was helpful!! I would suggest using a `HashSet` instead of a `List` since this will make `if (_properties.Contains(p.PropertyName))` faster. – Lea Hayes May 07 '14 at 22:08
  • Something of interest -- this does not work when inheriting from CamelCasePropertyNamesContractResolver. That class caches the results of the property generation and so you'll get the same results every time. Inheriting from DefaultContractResolver (as above) works fine though. – Rich Jan 07 '16 at 15:47
  • @Rich If you need camel casing you may simply just change JsonProperty.PropertyName to your camel case version. Very easy to implement yourself – fjch1997 Jun 12 '17 at 23:48
26

I had the exact same problem and looked up how to build the ContractResolver I was actually looking for and that better answer this question. This only serializes the Properties of the Type T you actually want to serialize, but with this example you can also easily build similar approaches:

public class TypeOnlyContractResolver<T> : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        property.ShouldSerialize = instance => property.DeclaringType == typeof (T);
        return property;
    }
}
Akku
  • 4,373
  • 4
  • 48
  • 67
  • 1
    Exactly what I was looking for - I have a base class that contains some properties that I don't want to be exposed. – Edi May 25 '16 at 08:36
  • Consider changing to `property.ShouldSerialize = instance => property.DeclaringType == typeof(T) || typeof(T).IsSubclassOf(property.DeclaringType);` if the class you are trying to serialize is a subclass of some other class – fjch1997 Jun 12 '17 at 23:57
  • Doesn't work for a complex object because the objects in a list will be empty objects. – Joost00719 Oct 05 '22 at 08:56
6

Having encountered a similar problem, this is the ContractResolver I came up with:

public class StrictTypeContractResolver : DefaultContractResolver
{
    private readonly Type _targetType;

    public StrictTypeContractResolver( Type targetType ) => _targetType = targetType;

    protected override IList<JsonProperty> CreateProperties( Type type, MemberSerialization memberSerialization )
        => base.CreateProperties
        (
            _targetType.IsAssignableFrom( type ) ? _targetType : type,
            memberSerialization
        );
}

It cuts off only the properties of targetType's descendants, without affecting the properties of its base classes or of other types that targetType's properties might reference. Which, depending on your needs, may or may not be an improvement over the other answers provided here at the time.

HellBrick
  • 557
  • 1
  • 6
  • 10
3

Check out the answers in this similar thread, particularly the IgnorableSerializerContractResolver in my answer and the nicer lambda version

Usage:

var jsonResolver = new IgnorableSerializerContractResolver();
// ignore single property
jsonResolver.Ignore(typeof(Company), "WebSites");
// ignore single datatype
jsonResolver.Ignore(typeof(System.Data.Objects.DataClasses.EntityObject));
var jsonSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, ContractResolver = jsonResolver };
Community
  • 1
  • 1
drzaus
  • 24,171
  • 16
  • 142
  • 201
1

Haven't compared performance implications, but this is a working solution as well, and works with nested/referenced objects as well.

Derived d = new Derived();           
string jsonStringD = JsonConvert.SerializeObject(d);
Base b = new Base();
JsonConvert.PopulateObject(jsonStringD, b);
string jsonStringB = JsonConvert.SerializeObject(b);
martinoss
  • 5,268
  • 2
  • 45
  • 53
1

I have not used JSON.Net in particular so not positive this will help you. If JSON.Net derives itself from the .Net serialization system then you should be able to add the [NonSerialized] attribute to your properties you do now wish to be serialized in the base class. When you call the serialize methods on the base class, serialization should then skip those elements.

Bueller
  • 2,336
  • 17
  • 11
  • 1
    Slightly different than what I want, which is that _all_ of the base class properties are produced, and _none_ of the derived class properties. I don't want to add `[NonSeralized]` to the derived class properties because there are times that I do want the derived class serialized as-is, just not when it's being used in a base class context. – Christopher Currie May 03 '11 at 17:36
  • These links may help. Appears you will have to perform custom serialization. [MSDN Article](http://msdn.microsoft.com/en-us/library/ty01x675(v=VS.100).aspx), [CodeProject Article](http://www.codeproject.com/KB/XML/CustomSerializationPart1.aspx) – Bueller May 03 '11 at 17:48
  • 1
    @Bueller This does not help if the class you inherit from is a native class which you can't modify. – Brad Moore Jul 27 '14 at 12:20