c# - Serializar solo propiedades de interfaz a JSON con Json.net
serialization (9)
Versión mejorada con interfaces anidadas + soporte para objetos xsd.exe
Otra variación más aquí. El código provino de tomdupont.net/2015/09/how-to-only-serialize-interface.html con las siguientes mejoras sobre otras respuestas aquí
- Maneja la jerarquía, por lo que si tiene una
Interface2[]
dentro de unaInterface1
, se serializará. Intentaba serializar un objeto proxy de WCF y el JSON resultante apareció como
{}
. Resultó que todas las propiedades se establecieron enIgnore=true
así que tuve que agregar un bucle para configurarlas todas para que no se ignoren.public class InterfaceContractResolver : DefaultContractResolver { private readonly Type[] _interfaceTypes; private readonly ConcurrentDictionary<Type, Type> _typeToSerializeMap; public InterfaceContractResolver(params Type[] interfaceTypes) { _interfaceTypes = interfaceTypes; _typeToSerializeMap = new ConcurrentDictionary<Type, Type>(); } protected override IList<JsonProperty> CreateProperties( Type type, MemberSerialization memberSerialization) { var typeToSerialize = _typeToSerializeMap.GetOrAdd( type, t => _interfaceTypes.FirstOrDefault( it => it.IsAssignableFrom(t)) ?? t); var props = base.CreateProperties(typeToSerialize, memberSerialization); // mark all props as not ignored foreach (var prop in props) { prop.Ignored = false; } return props; } }
Con una clase / interfaz simple como esta
public interface IThing
{
string Name { get; set; }
}
public class Thing : IThing
{
public int Id { get; set; }
public string Name { get; set; }
}
¿Cómo puedo obtener la cadena JSON con solo la propiedad "Nombre" (solo las propiedades de la interfaz subyacente)?
En realidad, cuando hago eso:
var serialized = JsonConvert.SerializeObject((IThing)theObjToSerialize, Formatting.Indented);
Console.WriteLine(serialized);
Obtengo el objeto completo como JSON (Id + Nombre);
Además de la respuesta dada por @monrow, puede usar el predeterminado [DataContract] y el [DataMember] echar un vistazo a esto
El método que uso,
public class InterfaceContractResolver : DefaultContractResolver
{
private readonly Type _InterfaceType;
public InterfaceContractResolver (Type InterfaceType)
{
_InterfaceType = InterfaceType;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
//IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
IList<JsonProperty> properties = base.CreateProperties(_InterfaceType, memberSerialization);
return properties;
}
}
// To serialize do this:
var settings = new JsonSerializerSettings() {
ContractResolver = new InterfaceContractResolver (typeof(IThing))
});
string json = JsonConvert.SerializeObject(theObjToSerialize, settings);
Finalmente recibí cuando no funcionaría ... Si quieres tener dentro de otro objeto complejo, no se serializará correctamente.
Así que hice una versión que extraerá solo los datos almacenados en un ensamblaje específico y para los tipos que tienen la misma interfaz base.
Por lo tanto, está hecho como .Net Core JsonContractResolver.
Además de la extracción de datos, resuelve:
a) conversión de camelCase antes de enviar datos al cliente
b) utiliza la interfaz más alta desde el alcance permitido (por ensamblaje) c) arregla el orden de los campos: el campo de la mayoría de la clase base se enumerará primero y el objeto anidado también cumplirá con esta regla.
public class OutputJsonResolver : DefaultContractResolver
{
#region Static Members
private static readonly object syncTargets = new object();
private static readonly Dictionary<Type, IList<JsonProperty>> Targets = new Dictionary<Type, IList<JsonProperty>>();
private static readonly Assembly CommonAssembly = typeof(ICommon).Assembly;
#endregion
#region Override Members
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
if (type.Assembly != OutputJsonResolver.CommonAssembly)
return base.CreateProperties(type, memberSerialization);
IList<JsonProperty> properties;
if (OutputJsonResolver.Targets.TryGetValue(type, out properties) == false)
{
lock (OutputJsonResolver.syncTargets)
{
if (OutputJsonResolver.Targets.ContainsKey(type) == false)
{
properties = this.CreateCustomProperties(type, memberSerialization);
OutputJsonResolver.Targets[type] = properties;
}
}
}
return properties;
}
protected override string ResolvePropertyName(string propertyName)
{
return propertyName.ToCase(Casing.Camel);
}
#endregion
#region Assistants
private IList<JsonProperty> CreateCustomProperties(Type type, MemberSerialization memberSerialization)
{
// Hierarchy
IReadOnlyList<Type> types = this.GetTypes(type);
// Head
Type head = types.OrderByDescending(item => item.GetInterfaces().Length).FirstOrDefault();
// Sources
IList<JsonProperty> sources = base.CreateProperties(head, memberSerialization);
// Targets
IList<JsonProperty> targets = new List<JsonProperty>(sources.Count);
// Repository
IReadOnlyDistribution<Type, JsonProperty> repository = sources.ToDistribution(item => item.DeclaringType);
foreach (Type current in types.Reverse())
{
IReadOnlyPage<JsonProperty> page;
if (repository.TryGetValue(current, out page) == true)
targets.AddRange(page);
}
return targets;
}
private IReadOnlyList<Type> GetTypes(Type type)
{
List<Type> types = new List<Type>();
if (type.IsInterface == true)
types.Add(type);
types.AddRange(type.GetInterfaces());
return types;
}
#endregion
}
Inspirado por @ user3161686, aquí hay una pequeña modificación de InterfaceContractResolver
:
public class InterfaceContractResolver<TInterface> : DefaultContractResolver where TInterface : class
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(typeof(TInterface), memberSerialization);
return properties;
}
}
Me gustaría compartir lo que terminamos haciendo cuando nos enfrentamos con esta tarea. Dada la interfaz y clase de OP ...
public interface IThing
{
string Name { get; set; }
}
public class Thing : IThing
{
public int Id { get; set; }
public string Name { get; set; }
}
... creamos una clase que es la implementación directa de la interfaz ...
public class DirectThing : IThing
{
public string Name { get; set; }
}
Luego simplemente serializó nuestra instancia Thing
, la deserializó como DirectThing
y luego la serializó como DirectThing
:
var thing = new Thing();
JsonConvert.SerializeObject(
JsonConvert.DeserializeObject<DirectThing>(JsonConvert.SerializeObject(thing)));
Este enfoque puede funcionar con una larga cadena de herencia de interfaz ... solo necesita hacer una clase directa ( DirectThing
en este ejemplo) al nivel de interés. No hay necesidad de preocuparse por la reflexión o los atributos.
Desde una perspectiva de mantenimiento, la clase DirectThing
es fácil de mantener si agrega miembros a IThing
porque el compilador dará errores si no los ha puesto también en DirectThing
. Sin embargo, si elimina un miembro X de IThing
y lo coloca en Thing
, tendrá que acordarse de eliminarlo de DirectThing
o de lo contrario X estaría en el resultado final.
Desde la perspectiva del rendimiento, hay tres (de) operaciones de serialización que suceden aquí en lugar de una, por lo que dependiendo de su situación, es posible que desee evaluar la diferencia de rendimiento de las soluciones basadas en reflector / atributo frente a esta solución. En mi caso, solo estaba haciendo esto en pequeña escala, por lo que no me preocupaban las pérdidas potenciales de algunos micro / milisegundos.
Espero que ayude a alguien!
Puede agregar la anotación [JsonIgnore]
para ignorar un atributo.
Puede usar la serialización condicional. Eche un vistazo a este link . Básicamente, debe implementar la interfaz IContractResolver
, sobrecargar el método ShouldSerialize
y pasar su resolver al constructor del Serializador Json.
Una alternativa a [JsonIgnore]
son los atributos [DataContract]
y [DataMember]
. Si su clase está etiquetada con [DataContract]
el serializador solo procesará las propiedades etiquetadas con el atributo [DataMember]
( JsonIgnore
es un modelo de "exclusión JsonIgnore
" mientras que DataContract
es "op-in").
[DataContract]
public class Thing : IThing
{
[DataMember]
public int Id { get; set; }
public string Name { get; set; }
}
La limitación de ambos enfoques es que deben implementarse en la clase, no puede agregarlos a la definición de la interfaz.