Json.NET 按深度和属性序列化

时间:2023-04-25
本文介绍了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 属性.因此,只有在解析嵌套引用属性(Depth > 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 为每个对象执行默认序列化,然后按照 在返回给客户端之前修改 JSON 的通用方法.请注意,这对树等递归结构有问题,因为转换器必须为子节点禁用自身以避免无限递归.

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 按深度和属性序列化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:你能有一个包含破折号的属性名称吗 下一篇:JsonConvert.SerializeObject 在 XamarinForms 中总是返回 {}

相关文章