忽略 Json.NET 序列化中的基类属性

19
我有以下类结构:
[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; }
    ...
}

我正在序列化Polygon类,但是当我这样做时,我会得到一个JsonSerializationException,错误信息为:

针对类型'MyNamespace.AxisAlignedRectangle'的属性'Envelope'检测到自引用循环。

如果我在AxisAlignedRectangle上添加[JsonObject(IsReference = true)](如此处所描述),代码就可以正常运行,但每个AxisAlignedRectangle实例都会有一个自动分配的$id字段,而在重新引用该实例时则会有一个$ref字段。例如,当我对多边形进行序列化时,我会得到:
{
    Vertices: [ ... ],
    Envelope: {
        $id: '1',
        Left: -5,
        ...
        Vertices: [ ... ],
        Envelope: {
            $ref: '1'
        }
    }
}

我希望在序列化AxisAlignedRectangle时完全删除多边形属性。 我尝试为AxisAlignedRectangle类添加DataContractAttribute(以及适当的DataMemberAttribute属性),但所有Polygon属性仍然被序列化。 这是出乎意料的,因为Json.NET文档中有一个示例,表明这种方法应该有效。 有人知道一种明确地从生成的Json.NET序列化中删除Envelope属性的方法吗? 特别感谢。
4个回答

29

最简单的方法是将 AxisAlignedRectangle 对象装饰上 [JsonObject(MemberSerialization.OptIn)]

简而言之,它只会序列化被 [JsonProperty] 属性装饰的属性。您可以在这里阅读更多内容:MemberSerialization 枚举

另一个选项是用 JsonIgnoreAttribute 类 装饰多边形属性。


该解决方案的问题在于,我只想禁用多边形子类中的1个子类中的Polygon.Envelope序列化。为了使您的建议起作用,我必须将Polygon.Envelope标记为虚拟的,然后在AxisAlignedRectangle中重写它,以添加JsonIgnoreAttribute。即使这样可以解决问题,我认为被标记为答案的解决方案更加清晰,因为代码明确表明只能更改其成员的序列化,而不能更改它们的基本行为。 - Jeff G
救了我的一天,谢谢。 - Dinesh Falwadiya

10
我也遇到了同样的问题。如果需要始终忽略某个属性并且可以访问包含该属性的类,则JsonIgnoreAttribute是一个好的解决方案。但是,如果您想在序列化时确定应序列化哪些属性,可以使用ContractResolver。
下面是一种实现方式,允许您将属性序列化从最具体的类开始,一直到给定的基类停止。在我的情况下,我想序列化自定义CMS(EPiServer)页面类型的属性,但不想序列化页面类的所有内置属性。
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;
    }
}

这让我可以像这样做:
JsonConvert.SerializeObject(page, new JsonSerializerSettings() 
    { 
         ContractResolver = new DerivedClassContractResolver(typeof(EPiServer.Core.PageData)) 
    }));

获取我自己定义的类上定义的属性,而不获取从EPiServer.Core.PageData继承而来的一堆属性。注意:如果您没有使用Castle DynamicProxy project(EPiServer CMS使用了该项目),则不需要GetOriginalType()代码。


1
太棒了,ContractResolver 正是我所需要的,也正是我认为 JsonSerializer 缺少的。非常方便! - quetzalcoatl
很棒的解决方案!谢谢你分享它 :) - JanDotNet

8
您可以使用条件属性序列化,通过以下方式定义您的类:
[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;
    }
}

我已经在以下链接发布了完整的解决方案: https://github.com/thiagoavelino/VisualStudio_C/blob/master/VisualStudio_C/StackOverFlow/ParsingJason/EnvelopePolygonProblem.cs


4
继承第三方类时无法正常工作。 - huang

0
在 WPF 中,忽略 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;
    }
}

这将忽略任何具有基类ObservableObject的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接