例如,我们有两个类
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
,该对每个对象执行默认序列化,然后按照在返回到客户端之前修改 JSON 的通用方法的行修剪不需要的属性。 请注意,这在递归结构(如树)方面存在问题,因为转换器必须为子节点禁用自身以避免无限递归。
另一种可能性是创建自定义IContractResolver
,该根据序列化深度为每个类型返回不同的协定。 这必须使用序列化回调来跟踪对象序列化的开始和结束时间,因为协定解析器不知道序列化深度:
[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()
的调用。
此解决方案假定您不想更改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 时,它将始终忽略标有该属性的字段。