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

标签 c# .net 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)时,我们才应该得到一个字段。

输出应该是:{ "FooA": { "SomeValueA": "0"} }

有什么想法吗?

最佳答案

这里的基本难点在于,Json.NET 是一个基于契约的序列化程序,它为每个要序列化的类型创建一个契约,然后根据契约进行序列化。无论类型出现在对象图中的什么位置,都适用相同的契约。但是您希望根据给定类型在对象图中的深度有选择地包含其属性,这与基本的“一种类型一种契约”设计相冲突,因此需要一些工作。

完成您想要的事情的一种方法是创建一个 JsonConverter为每个对象执行默认序列化,然后按照 Generic method of modifying JSON before being returned to client 的行修剪不需要的属性.请注意,这对于树等递归结构存在问题,因为转换器必须为子节点禁用自身以避免无限递归。

另一种可能性是创建一个 custom IContractResolver根据序列化深度为每种类型返回不同的契约(Contract)。这必须需要利用 serialization callbacks跟踪对象序列化何时开始和结束,因为序列化深度不为契约解析器所知:

[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
}

然后如下标记你的类:

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().

关于c# - Json.NET 按深度和属性序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36159424/

相关文章:

c# - 使用 Regex.Split 删除任何非数字和拆分 -

c# - .NET 以与加载时相同的质量保存 jpeg

c# - 如何获取标签和 NumericUpDown 中文本基线的位置?

.net - ADO.NET 中是否有工厂方法来获取参数标志?

c# - Json.net 属性 : single value or array of values

c# - 从 JObject 中删除重复项

java - 如何从彼此不关注的数字池中生成一个随机数

c# - 获取只有文件扩展名的 Shell Icon

c# - 如何在JSON数组中按键获取元素

c# - 如何在C#中处理excel工作表的另存为对话框