Ignore Base Class Properties in Json.NET Serialization
Asked Answered
S

4

19

I have the following class structure:

[JsonObject]
public class Polygon : IEnumerable<Point>
{
    public List<Point> Vertices { get; set; }
    public AxisAlignedRectangle Envelope { get; set; }
}

public class AxisAlignedRectangle : Polygon {
    public double Left { get; set; }
    ...
}

I am serializing the Polygon class, but when I do, I get a JsonSerializationException, with the message

Self referencing loop detected for property 'Envelope' with type 'MyNamespace.AxisAlignedRectangle'.

If I add [JsonObject(IsReference = true)] (as described here) to AxisAlignedRectangle, the code runs fine, but I get an auto-assigned $id field in each instance of AxisAlignedRectangle, and a $ref field when that instance is re-referenced. For example, when I serialize a polygon, I get:

{
    Vertices: [ ... ],
    Envelope: {
        $id: '1',
        Left: -5,
        ...
        Vertices: [ ... ],
        Envelope: {
            $ref: '1'
        }
    }
}

My desire is to remove the Polygon properties entirely when I serialize an AxisAlignedRectangle. I tried adding a DataContractAttribute to the AxisAlignedRectangle class (along with appropriate DataMemberAttribute attributes), but all the properties of Polygon were still being serialized. This was unexpected, since there is an example in the Json.NET documentation that appears to indicate such an approach should work.

Does anyone know a way to explicitly remove (most importantly) the Envelope property from the resulting Json.NET serialization, when the type being serialized is AxisAlignedRectangle? Thanks.

Susy answered 28/6, 2015 at 20:17 Comment(0)
D
8

You can use conditional property serialization, by defining your classes like this:

[JsonObject]
public class Polygon : IEnumerable<Point>
{
    public List<Point> Vertices { get; set; }
    public AxisAlignedRectangle Envelope { get; set; }

    public virtual bool ShouldSerializeEnvelope()
    {
        return true;
    }
}

public class AxisAlignedRectangle : Polygon
{
    public double Left { get; set; }
    ...

    public override bool ShouldSerializeEnvelope()
    {
        return false;
    }
}

I have posted the full solution at: https://github.com/thiagoavelino/VisualStudio_C/blob/master/VisualStudio_C/StackOverFlow/ParsingJason/EnvelopePolygonProblem.cs

Determined answered 28/6, 2015 at 22:31 Comment(1)
Not work when you inherit from a third-party class.Maritime
L
29

Most simple way to do it is simply decorate the AxisAlignedRectangle object with [JsonObject(MemberSerialization.OptIn)].

In a sentence, it will serialize only properties decorated with [JsonProperty] attribute. You can read more here: MemberSerialization Enumeration.

Another option is to decorate the Polygon properties with JsonIgnoreAttribute Class.

Lilah answered 26/1, 2016 at 21:57 Comment(2)
The problem with this solution is that I only want to disable serialization of Polygon.Envelope in 1 of the N subclasses of Polygon. To get your suggestion to work, I would have to mark Polygon.Envelope virtual, then override it in AxisAlignedRectangle, just to add JsonIgnoreAttribute. Even if that works, I think the solution marked as answer is cleaner, since the code makes it clear that only the serialization of its members can be changed, not their underlying behavior.Susy
Saved my day , ThanksDickson
I
10

I've run into the same thing. The JsonIgnoreAttribute is a good solution if a certain property should always be ingored and you have access to the class containing the property. But if you want to determine which properties should be serialized at serialization-time, you can use a ContractResolver.

Here's an implementation that allows you to serialize properties starting with the most derived class and stopping at a given base class. In my case, I wanted to serialize the properties of my custom CMS (EPiServer) page types, but didn't want to serialize all of the built-in properties of the page classes.

public class DerivedClassContractResolver : DefaultContractResolver
{
    private Type _stopAtBaseType;

    public DerivedClassContractResolver(Type stopAtBaseType) 
    {
        _stopAtBaseType = stopAtBaseType;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        Type originalType = GetOriginalType(type);
        IList<JsonProperty> defaultProperties = base.CreateProperties(type, memberSerialization);
        List<string> includedProperties = Utilities.GetPropertyNames(originalType, _stopAtBaseType);

        return defaultProperties.Where(p => includedProperties.Contains(p.PropertyName)).ToList();
    }

    private Type GetOriginalType(Type type)
    {
        Type originalType = type;

        //If the type is a dynamic proxy, get the base type
        if (typeof(Castle.DynamicProxy.IProxyTargetAccessor).IsAssignableFrom(type))
            originalType = type.BaseType ?? type;

        return originalType;
    }
}

public class Utilities
{
    /// <summary>
    /// Gets a list of all public instance properties of a given class type
    /// excluding those belonging to or inherited by the given base type.
    /// </summary>
    /// <param name="type">The Type to get property names for</param>
    /// <param name="stopAtType">A base type inherited by type whose properties should not be included.</param>
    /// <returns></returns>
    public static List<string> GetPropertyNames(Type type, Type stopAtBaseType)
    {
        List<string> propertyNames = new List<string>();

        if (type == null || type == stopAtBaseType) return propertyNames; 

        Type currentType = type;

        do
        {
            PropertyInfo[] properties = currentType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);

            foreach (PropertyInfo property in properties)
                if (!propertyNames.Contains(property.Name))
                    propertyNames.Add(property.Name);

            currentType = currentType.BaseType;
        } while (currentType != null && currentType != stopAtBaseType);

        return propertyNames;
    }
}

This let's me do something like this:

JsonConvert.SerializeObject(page, new JsonSerializerSettings() 
    { 
         ContractResolver = new DerivedClassContractResolver(typeof(EPiServer.Core.PageData)) 
    }));

to get the properties I have defined on my own class(es) without getting the slew of properties inherited from EPiServer.Core.PageData. Note: You don't need the GetOriginalType() code if you're not using the Castle DynamicProxy project (which the EPiServer CMS does.)

Invasive answered 25/2, 2016 at 18:5 Comment(2)
Brillant, ContractResolver was exacly what I needed and what I thought the JsonSerializer is missing. Soo handy!Earlap
Great solution! Thanks for sharing it :)Ratiocination
D
8

You can use conditional property serialization, by defining your classes like this:

[JsonObject]
public class Polygon : IEnumerable<Point>
{
    public List<Point> Vertices { get; set; }
    public AxisAlignedRectangle Envelope { get; set; }

    public virtual bool ShouldSerializeEnvelope()
    {
        return true;
    }
}

public class AxisAlignedRectangle : Polygon
{
    public double Left { get; set; }
    ...

    public override bool ShouldSerializeEnvelope()
    {
        return false;
    }
}

I have posted the full solution at: https://github.com/thiagoavelino/VisualStudio_C/blob/master/VisualStudio_C/StackOverFlow/ParsingJason/EnvelopePolygonProblem.cs

Determined answered 28/6, 2015 at 22:31 Comment(1)
Not work when you inherit from a third-party class.Maritime
D
0

In WPF its common to want to ignore Observables

public class TypeOnlyContractResolver<T> : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        property.ShouldSerialize = instance =>
        {
            return !property.DeclaringType.AssemblyQualifiedName.Contains("Observable");
        };

        property.ShouldDeserialize = instance =>
        {
            return !property.DeclaringType.AssemblyQualifiedName.Contains("Observable");
        };

        return property;
    }
}

This will ignore anything with the base class ObservableObject etc.

Doublet answered 9/6, 2022 at 2:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.