Pregunta Serializar solo propiedades de interfaz a JSON con Json.net


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


24
2018-06-15 12:49


origen


Respuestas:


Puede usar la serialización condicional. Mira esto enlazar. Básicamente, necesita implementar el IContractResolver interfaz, sobrecargar el ShouldSerialize método y pasar su resolver al constructor del Serializador Json.


9
2018-06-15 13:30



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

19
2018-01-05 02:23



Inspirado por @ user3161686, aquí hay una pequeña modificación a 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;
    }
}

14
2018-04-03 22:33



Versión mejorada con interfaces anidadas + soporte para objetos xsd.exe

Otra variación más aquí. El código vino de http://www.tomdupont.net/2015/09/how-to-only-serialize-interface.html con las siguientes mejoras sobre otras respuestas aquí

  • Maneja la jerarquía, así que si tienes una Interface2[] dentro de un Interface1 luego se serializará.
  • Estaba tratando de serializar un objeto proxy WCF y el JSON resultante surgió como {}. Resultó que todas las propiedades estaban configuradas para Ignore=true así que tuve que agregar un bucle para configurarlos para que no los ignoraran.

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

6
2018-01-13 04:39



Puedes agregar el [JsonIgnore] anotación para ignorar un atributo.


5
2018-06-15 12:56



Una alternativa a [JsonIgnore] son el [DataContract] y [DataMember] atributos. Si tu clase está etiquetada con [DataContract] el serializador solo procesará las propiedades etiquetadas con el [DataMember] atributo (JsonIgnore es un modelo de "exclusión voluntaria" mientras 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.


5
2017-08-06 17:00



Además de la respuesta dada por @monrow, puede usar el [DataContract] predeterminado y el [DataMember] Echa un vistazo a esto

http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size.aspx


2
2018-06-15 13:00



Finalmente llegué cuando no funcionará ... Si desea 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á 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
}

1
2018-06-23 01:04



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

Entonces simplemente serializamos nuestra Thing ejemplo, lo deserializó como DirectThing, luego lo serializó como un 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 (DirectThingen este ejemplo) al nivel de interés. No hay necesidad de preocuparse por la reflexión o los atributos.

Desde una perspectiva de mantenimiento, el DirectThing la clase es fácil de mantener si agrega miembros a IThing porque el compilador dará errores si no los has puesto también DirectThing. Sin embargo, si retirar un miembro X de IThing  y ponlo adentro Thing en su lugar, tendrás que recordar quitarlo de DirectThing 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!


1
2018-01-19 15:49