【问题标题】:Json.NET serialize by depth and attributeJson.NET 按深度和属性序列化
【发布时间】:2016-03-22 16:04:21
【问题描述】:

例如我们有两个类

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

有什么想法吗?

【问题讨论】:

    标签: c# .net json.net


    【解决方案1】:

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

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

    另一种可能性是创建一个custom IContractResolver,它根据序列化深度为每种类型返回不同的合约。这必须使用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() 的调用。

    【讨论】:

      【解决方案2】:

      此解决方案假定您不想更改 FooA 类的所有序列化。如果是这种情况,您应该创建自己的 JsonConverter。

      public class FooConverter : JsonConverter
      {      
         public FooConveter(params Type[] parameterTypes)
         {
      
         }
      
         public override bool CanConvert(Type objectType)
         {
            return objectType.IsAssignableFrom(typeof(FooA));
         }
      
         public override object ReadJson(JsonReader reader, Type objectType)
         {
            //Put your code to deserialize FooA here.  
            //You probably don't need it based on the scope of your question.
         }
      
         public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
         {
            //Code to serialize FooA.
            if (value == null)
            {
               writer.WriteNull();
               return;
            }
      
            //Only serialize SomeValueA
            var foo = value as FooA;
            writer.WriteStartObject();
            writer.WritePropertyName("FooA");
            writer.Serialize(writer, foo.SomeValueA);
            writer.WriteEndObject();          
         }
      }
      

      在你的代码中使用你的转换器

      class FooB
      {
         [FooConverter]
         public FooA FooA { get; set; }
      }
      

      否则,您可以使用JsonIgnore 属性忽略FooA 中您不想序列化的字段。请记住,这里的权衡是每当您将 FooA 转换为 Json 时,它总是会忽略标有该属性的字段。

      【讨论】:

      • 也许可以在合约解析器级别实现某些东西?但似乎它不处理引用的属性并且没有深度信息..不是递归的。
      最近更新 更多