Json.NET serialize por profundidad y atributo

Por ejemplo, tenemos dos clases

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

Yo uso Json.NET, la profundidad máxima es 1. Mientras serializo FooA, debería mostrar todas las propiedades como de costumbre, pero al serializar FooB debería generar solo una propiedad de FooA que tenga un atributo especial. Entonces, solo al resolver las propiedades de referencia anidadas (Profundidad> 0) deberíamos obtener un solo campo.

La salida debería ser: {“FooA”: {“SomeValueA”: “0”}}

¿Algunas ideas?

La dificultad básica aquí es que Json.NET es un serializador basado en contratos que crea un contrato para cada tipo que se serializará, luego se serializa de acuerdo con el contrato. No importa dónde aparezca un tipo en el gráfico de objetos, se aplica el mismo contrato. Pero desea incluir selectivamente propiedades para un tipo dado, dependiendo de su profundidad en el gráfico de objetos, que entra en conflicto con el diseño básico de “un contrato de tipo uno” y, por lo tanto, requiere cierto trabajo.

Una forma de lograr lo que desea sería crear un JsonConverter que realice una serialización predeterminada para cada objeto, luego pode las propiedades no deseadas, siguiendo las líneas del método genérico de modificación de JSON antes de devolverlo al cliente . Tenga en cuenta que esto tiene problemas con las estructuras recursivas, como los árboles, porque el convertidor debe deshabilitarse para los nodos secundarios para evitar la recursión infinita.

Otra posibilidad sería crear un IContractResolver personalizado que devuelva un contrato diferente para cada tipo dependiendo de la profundidad de serialización. Esto debe hacer uso de devoluciones de serialización para rastrear cuándo comienza y finaliza la serialización de objetos, ya que la profundidad de serialización no se da a conocer al resolver del contrato:

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

Luego marque sus clases de la siguiente manera:

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

Y serializa de la siguiente manera:

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

El CreateTracker() ligeramente torpe es necesario para garantizar que, en el caso de que se CreateTracker() una excepción a mitad de la serialización, la profundidad del objeto actual se restablezca y no afecte futuras llamadas a JsonConvert.SerializeObject() .

Esta solución supone que no desea cambiar toda la serialización de la clase FooA . Si este es el caso, debe crear su propio 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(); } } 

Y usa tu convertidor en tu código como

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

De lo contrario, puede usar el atributo JsonIgnore para ignorar los campos en FooA que no desea serializar. Tenga en cuenta que la compensación es que siempre que convierta FooA en Json, siempre ignorará los campos marcados con ese atributo.