Json.NET按深度和属性进行序列化 [英] Json.NET serialize by depth and attribute

查看:73
本文介绍了Json.NET按深度和属性进行序列化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

例如,我们有两个类

class FooA
{
    [SomeSpecialAttribute]
    public int SomeValueA { get; set; }

    public int SomeValueB { get; set; }

    public int SomeValueC { get; set; }
}

class FooB
{
    public FooA FooA { get; set; }
}

我使用Json.NET,最大深度为1.序列化FooA时,它应照常输出所有属性,但是序列化FooB时,它应仅输出一个具有特殊属性的FooA属性.因此,只有在解析嵌套引用属性(深度> 0)时,我们才应该获得单个字段.

I use Json.NET, max depth is 1. While serializing FooA it should output all properties as usual, but while serializing FooB it should output only one FooA's property which has special attribute. So only while resolving nested reference properties (Depth > 0) we should get a single field.

输出应为:{"FooA":{"SomeValueA":"0"}}

Output should be: { "FooA": { "SomeValueA": "0" } }

有什么想法吗?

推荐答案

这里的基本困难是Json.NET是基于契约的序列化器,它为要序列化的每种类型创建契约,然后根据契约进行序列化.无论类型在对象图中出现在何处,都应应用相同的合同.但是您要根据对象图中对象的深度有选择地包括给定类型的属性,这与基本的一种类型一个合约"设计冲突,因此需要做一些工作.

The basic difficulty here is that Json.NET is a contract-based serializer which creates a contract for each type to be serialized, then serializes according to the contract. No matter where a type appears in the object graph, the same contract applies. But you want to selectively include properties for a given type depending on its depth in the object graph, which conflicts with the basic "one type one contract" design and thus requires some work.

一种实现所需目标的方法是创建一个 JsonConverter 为每个对象执行默认序列化,然后按照

One way to accomplish what you want would be to create a JsonConverter that performs a default serialization for each object, then prunes undesired properties, along the lines of Generic method of modifying JSON before being returned to client. Note that this has problems with recursive structures such as trees, because the converter must disable itself for child nodes to avoid infinite recursion.

另一种可能性是创建一个自定义IContractResolver 根据序列化深度为每种类型返回不同的协定.这必须利用串行化回调来跟踪对象序列化何时开始以及结束,因为合同解析器不知道序列化深度:

Another possibility would be to create a custom IContractResolver that returns a different contract for each type depending on the serialization depth. This must needs make use of serialization callbacks to track when object serialization begins and ends, since serialization depth is not made known to the contract resolver:

[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)]
public class JsonIncludeAtDepthAttribute : System.Attribute
{
    public JsonIncludeAtDepthAttribute()
    {
    }
}

public class DepthPruningContractResolver : IContractResolver
{
    readonly int depth;

    public DepthPruningContractResolver()
        : this(0)
    {
    }

    public DepthPruningContractResolver(int depth)
    {
        if (depth < 0)
            throw new ArgumentOutOfRangeException("depth");
        this.depth = depth;
    }

    [ThreadStatic]
    static DepthTracker currentTracker;

    static DepthTracker CurrentTracker { get { return currentTracker; } set { currentTracker = value; } }

    class DepthTracker : IDisposable
    {
        int isDisposed;
        DepthTracker oldTracker;

        public DepthTracker()
        {
            isDisposed = 0;
            oldTracker = CurrentTracker;
            currentTracker = this;
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (0 == Interlocked.Exchange(ref isDisposed, 1))
            {
                CurrentTracker = oldTracker;
                oldTracker = null;
            }
        }
        #endregion

        public int Depth { get; set; }
    }

    abstract class DepthTrackingContractResolver : DefaultContractResolver
    {
        static DepthTrackingContractResolver() { } // Mark type with beforefieldinit.

        static SerializationCallback OnSerializing = (o, context) =>
        {
            if (CurrentTracker != null)
                CurrentTracker.Depth++;
        };

        static SerializationCallback OnSerialized = (o, context) =>
        {
            if (CurrentTracker != null)
                CurrentTracker.Depth--;
        };

        protected override JsonObjectContract CreateObjectContract(Type objectType)
        {
            var contract = base.CreateObjectContract(objectType);
            contract.OnSerializingCallbacks.Add(OnSerializing);
            contract.OnSerializedCallbacks.Add(OnSerialized);
            return contract;
        }
    }

    sealed class RootContractResolver : DepthTrackingContractResolver
    {
        // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
        // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
        // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
        // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
        static RootContractResolver instance;
        static RootContractResolver() { instance = new RootContractResolver(); }
        public static RootContractResolver Instance { get { return instance; } }
    }

    sealed class NestedContractResolver : DepthTrackingContractResolver
    {
        static NestedContractResolver instance;
        static NestedContractResolver() { instance = new NestedContractResolver(); }
        public static NestedContractResolver Instance { get { return instance; } }

        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var property = base.CreateProperty(member, memberSerialization);

            if (property.AttributeProvider.GetAttributes(typeof(JsonIncludeAtDepthAttribute), true).Count == 0)
            {
                property.Ignored = true;
            }

            return property;
        }
    }

    public static IDisposable CreateTracker()
    {
        return new DepthTracker();
    }

    #region IContractResolver Members

    public JsonContract ResolveContract(Type type)
    {
        if (CurrentTracker != null && CurrentTracker.Depth > depth)
            return NestedContractResolver.Instance.ResolveContract(type);
        else
            return RootContractResolver.Instance.ResolveContract(type);
    }

    #endregion
}

然后将您的课程标记如下:

Then mark your classes as follows:

class FooA
{
    [JsonIncludeAtDepthAttribute]
    public int SomeValueA { get; set; }

    public int SomeValueB { get; set; }

    public int SomeValueC { get; set; }
}

class FooB
{
    public FooA FooA { get; set; }
}

并序列化如下:

var settings = new JsonSerializerSettings { ContractResolver = new DepthPruningContractResolver(depth), Formatting = Formatting.Indented };

using (DepthPruningContractResolver.CreateTracker())
{
    var jsonB = JsonConvert.SerializeObject(foob, settings);
    Console.WriteLine(jsonB);

    var jsonA = JsonConvert.SerializeObject(foob.FooA, settings);
    Console.WriteLine(jsonA);
}

需要稍微笨拙的CreateTracker()以确保在序列化过程中抛出异常的情况下,当前对象的深度被重置并且不会影响以后对JsonConvert.SerializeObject()的调用.

The slightly awkward CreateTracker() is needed to ensure that, in the event an exception is thrown partway through serialization, the current object depth gets reset and does not affect future calls to JsonConvert.SerializeObject().

这篇关于Json.NET按深度和属性进行序列化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆