简体   繁体   English

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

[英]Json.NET serialize by depth and attribute

For example we have two classes 例如我们有两个类

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

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. 我使用Json.NET,最大深度为1。在序列化FooA时,它应该照常输出所有属性,但是在序列化FooB时,它应该仅输出一个具有特殊属性的FooA的属性。 So only while resolving nested reference properties (Depth > 0) we should get a single field. 因此,只有在解析嵌套引用属性(深度> 0)时,我们才应该获得一个字段。

Output should be: { "FooA": { "SomeValueA": "0" } } 输出应为:{“ FooA”:{“ SomeValueA”:“ 0”}}

Any ideas? 有任何想法吗?

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. 这里的基本困难是Json.NET是基于契约的序列化器,它为要序列化的每种类型创建契约,然后根据契约进行序列化。 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. 但是您想要根据给定类型在对象图中的深度来选择性地包含给定类型的属性,这与基本的“一种类型一个合同”设计冲突,因此需要进行一些工作。

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 . 一种实现所需目标的方法是创建一个JsonConverter ,该对象对每个对象执行默认的序列化,然后按照修改JSON通用方法的方式修剪不想要的属性, 然后再返回给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. 请注意,这对于诸如树的递归结构存在问题,因为转换器必须为子节点禁用自身以避免无限递归。

Another possibility would be to create a custom IContractResolver that returns a different contract for each type depending on the serialization depth. 另一种可能性是创建一个自定义的IContractResolver ,它根据序列化深度为每种类型返回不同的协定。 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; }
}

And serialize as follows: 并序列化如下:

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);
}

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() . 需要稍微笨拙的CreateTracker()来确保在序列化过程中抛出异常的情况下,重置当前对象的深度,并且不影响以后对JsonConvert.SerializeObject()调用。

This solution assumes you don't want to change all serialization of the FooA class. 该解决方案假定您不想更改FooA类的所有序列化。 If this is the case, you should create your own JsonConverter. 在这种情况下,您应该创建自己的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();          
   }
}

And use your converter in your code as 并在代码中使用转换器

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

Otherwise, you can use the JsonIgnore attribute to ignore the fields in FooA that you don't want serialized. 否则,您可以使用JsonIgnore属性来忽略FooA中您不想序列化的字段。 Keep in mind, the tradeoff there is that whenever you convert FooA to Json, it will always ignore fields marked with that attribute. 请记住,需要权衡的是,每当将FooA转换为Json时,它将始终忽略标记有该属性的字段。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM