Hacer que una propiedad se deserialice pero no se serialice con json.net

124

Tenemos algunos archivos de configuración que se generaron serializando objetos C # con Json.net.

Nos gustaría migrar una propiedad de la clase serializada de ser una propiedad de enumeración simple a una propiedad de clase.

Una forma fácil de hacer esto sería dejar la antigua propiedad enum en la clase y hacer que Json.net lea esta propiedad cuando carguemos la configuración, pero no la vuelva a guardar la próxima vez que serialicemos el objeto. Nos ocuparemos de generar la nueva clase a partir de la enumeración anterior por separado.

¿Existe alguna forma sencilla de marcar (por ejemplo, con atributos) una propiedad de un objeto C #, de modo que Json.net lo ignore SOLAMENTE al serializar, pero lo atenderá al deserializar?

Will Dean
fuente
¿Qué pasa con un convertidor personalizado: puede usarlo como un atributo en su propiedad, anular ReadJson y WriteJson con diferentes comportamientos, no? Ejemplo (no es exactamente lo que necesita, pero ...) weblogs.asp.net/thangchung/archive/2010/08/26/…
Raphaël Althaus
El atributo OnDeserialized puede ser una solución para usted
Estefany Velez
¿No debería ser posible usando el atributo '[JsonIgnore]' ?? james.newtonking.com/archive/2009/10/23/…
Juri
¿Puede expandir sobre cómo esto se puede usar en una sola dirección, según el último párrafo de la q?
Will Dean
Es posible usar [JsonIgnore] en combinación con un setter privado secundario que está decorado con un atributo [JsonProperty]. También hay un par de otras soluciones simples. Agregué un informe detallado.
Brian Rogers

Respuestas:

133

En realidad, existen varios enfoques bastante simples que puede utilizar para lograr el resultado que desea.

Supongamos, por ejemplo, que tiene sus clases actualmente definidas así:

class Config
{
    public Fizz ObsoleteSetting { get; set; }
    public Bang ReplacementSetting { get; set; }
}

enum Fizz { Alpha, Beta, Gamma }

class Bang
{
    public string Value { get; set; }
}

Y quieres hacer esto:

string json = @"{ ""ObsoleteSetting"" : ""Gamma"" }";

// deserialize
Config config = JsonConvert.DeserializeObject<Config>(json);

// migrate
config.ReplacementSetting = 
    new Bang { Value = config.ObsoleteSetting.ToString() };

// serialize
json = JsonConvert.SerializeObject(config);
Console.WriteLine(json);

Para conseguir esto:

{"ReplacementSetting":{"Value":"Gamma"}}

Enfoque 1: agregar un método ShouldSerialize

Json.NET tiene la capacidad de serializar condicionalmente propiedades buscando los ShouldSerializemétodos correspondientes en la clase.

Para usar esta función, agregue un ShouldSerializeBlah()método booleano a su clase donde Blahse reemplaza con el nombre de la propiedad que no desea serializar. Haz que la implementación de este método siempre regrese false.

class Config
{
    public Fizz ObsoleteSetting { get; set; }

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

Nota: si le gusta este enfoque pero no quiere enturbiar la interfaz pública de su clase introduciendo un ShouldSerializemétodo, puede usar an IContractResolverpara hacer lo mismo mediante programación. Consulte Serialización de propiedad condicional en la documentación.

Enfoque 2: manipular JSON con JObjects

En lugar de usar JsonConvert.SerializeObjectpara hacer la serialización, cargue el objeto de configuración en unJObject , luego simplemente elimine la propiedad no deseada del JSON antes de escribirlo. Son solo un par de líneas adicionales de código.

JObject jo = JObject.FromObject(config);

// remove the "ObsoleteSetting" JProperty from its parent
jo["ObsoleteSetting"].Parent.Remove();

json = jo.ToString();

Enfoque 3: uso inteligente (ab) de los atributos

  1. Aplicar un [JsonIgnore] atributo a la propiedad que no desea serializar.
  2. Agrega una alternativa, establecedor de propiedad privada a la clase con el mismo tipo que la propiedad original. Haga que la implementación de esa propiedad establezca la propiedad original.
  3. Aplicar un [JsonProperty] atributo al establecedor alternativo, dándole el mismo nombre JSON que la propiedad original.

Aquí está la Configclase revisada :

class Config
{
    [JsonIgnore]
    public Fizz ObsoleteSetting { get; set; }

    [JsonProperty("ObsoleteSetting")]
    private Fizz ObsoleteSettingAlternateSetter
    {
        // get is intentionally omitted here
        set { ObsoleteSetting = value; }
    }

    public Bang ReplacementSetting { get; set; }
}
Brian Rogers
fuente
7
Lo solucionamos en nuestro proyecto (que usa un superconjunto interno específico de integración del modelo base, donde ninguna de las propiedades de la superclase debe ser serializada), estableciendo get-properties en internal. Tener establecedores públicos permitió a Web Api establecer las propiedades, pero impidió que las serializara.
Daniel Saidi
7
Junto con el uso de JsonPropertyAttribute, desde C # 6.0 puede usar la nameofpalabra clave en lugar de usar "cadenas mágicas". Esto hace que la refactorización sea mucho más fácil e infalible; además, si no cambia el nombre de alguna ocurrencia, el compilador le advertirá de todos modos. Usando el ejemplo de @ Brian, el uso sería el siguiente:[JsonProperty(nameof(ObsoleteSetting))]
Geoff James
1
Es una mala idea usar nameof () en declaraciones de JsonProperty, particularmente en este escenario heredado. El JSON representa un contrato externo (y con suerte eterno) con otra interfaz, y definitivamente NO desea cambiar el nombre de la propiedad JSON si refactoriza. Estaría rompiendo la compatibilidad con todos los archivos y componentes JSON existentes que generan JSON en este formato. De hecho, sería mejor poner JsonProperty (...) con el nombre completo en cada propiedad serializada para asegurarse de que no cambien si luego cambia el nombre de la propiedad.
Ammo Goettsch
36

Para cualquier situación en la que sea aceptable que su propiedad de solo deserialización se marque como interna, existe una solución notablemente simple que no depende en absoluto de los atributos. Simplemente marque la propiedad como get interno, pero conjunto público:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

Esto da como resultado una deserialización correcta utilizando la configuración / resolutores / etc. Predeterminados, pero la propiedad se elimina de la salida serializada.

Iucounu
fuente
Solución simple pero inteligente.
hbulens
Tenga en cuenta que la propiedad también será ignorada por el módulo de validación. (Por lo tanto, ya no puede marcarlo como [Requerido] para la deserialización, ya que esto depende de un getmétodo público ).
Martin Hansen
2
Esto no funciona con internalni con private. Siempre está serializado.
Paul
Esto no funcionó para mí. Obtuve un error de propiedad no encontrada al deserializar.
Drew Sumido
31

Me gusta apegarme a los atributos en este, aquí está el método que uso cuando necesito deserializar una propiedad pero no serializarla o viceversa.

PASO 1: cree el atributo personalizado

public class JsonIgnoreSerializationAttribute : Attribute { }

PASO 2: Cree una renovación de contrato personalizada

class JsonPropertiesResolver : DefaultContractResolver
{
    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        //Return properties that do NOT have the JsonIgnoreSerializationAttribute
        return objectType.GetProperties()
                         .Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute)))
                         .ToList<MemberInfo>();
    }
}

PASO 3: agregue un atributo donde la serialización no es necesaria pero la deserialización es

    [JsonIgnoreSerialization]
    public string Prop1 { get; set; } //Will be skipped when serialized

    [JsonIgnoreSerialization]
    public string Prop2 { get; set; } //Also will be skipped when serialized

    public string Prop3 { get; set; } //Will not be skipped when serialized

PASO 4 - Úselo

var sweet = JsonConvert.SerializeObject(myObj, new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() });

¡Espero que esto ayude! También vale la pena señalar que esto también ignorará las propiedades cuando ocurra la deserialización, cuando estoy deserializando, solo uso el convertidor de la manera convencional.

JsonConvert.DeserializeObject<MyType>(myString);
Jraco11
fuente
Gracias por esta útil implementación. ¿Hay alguna manera de extender la implementación base en GetSerializableMemberslugar de anularla por completo?
Alain
4
No importa, acabo de darme cuenta de que era tan simple como:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Alain
No estoy seguro de por qué esta no es la respuesta mejor calificada. es limpio, sigue los patrones de newtonsoft y es fácil de hacer. Lo único que agregaría es que puede configurarlo globalmente usandoJsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Matt M
1
no importa, esto en realidad no hace lo que pide el solicitante. esto es básicamente recreando el JsonIgnore. no omite la propiedad para la serialización, pero establece la propiedad durante la deserialización porque no hay forma de que el método GetSerializableMembers sepa si es una lectura o una escritura, por lo que está excluyendo las propiedades para ambos. esta solución no funciona.
Matt M
7

Utilice la propiedad del setter:

[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { _ignoreOnSerializing = value; } }

[JsonIgnore]
private string _ignoreOnSerializing;

[JsonIgnore]
public string IgnoreOnSerializing
{
    get { return this._ignoreOnSerializing; }
    set { this._ignoreOnSerializing = value; }
}

Espero que esto ayude.

Tho Ho
fuente
1
Gracias. Tenga en cuenta que JsonProperty debe tener mayúsculas IgnoreOnSerializing, igual a la propiedad. Recomiendo usar nameof(IgnoreOnSerializing)para evitar la cadena mágica, en caso de cambiar el nombre.
Bendik August Nesbø
5

Después de pasar bastante tiempo buscando cómo marcar una propiedad de clase para ser Desserializable y NO Serializable, descubrí que no existe tal cosa para hacer eso; así que se me ocurrió una solución que combina dos bibliotecas o técnicas de serialización diferentes (System.Runtime.Serialization.Json & Newtonsoft.Json) y funcionó para mí de la siguiente manera:

  • marca todas tus clases y subclases como "DataContract".
  • marque todas las propiedades de su clase y subclases como "DataMember".
  • Marque todas las propiedades de su clase y subclases como "JsonProperty", excepto aquellas que desee que no se serialicen.
  • ahora marque las propiedades que NO desea que se serialicen como "JsonIgnore".
  • luego serialice usando "Newtonsoft.Json.JsonConvert.SerializeObject" y De-Serialize usando "System.Runtime.Serialization.Json.DataContractJsonSerializer".

    using System;
    using System.Collections.Generic;
    using Newtonsoft.Json;
    using System.Runtime.Serialization;
    using System.IO;
    using System.Runtime.Serialization.Json;
    using System.Text;
    
    namespace LUM_Win.model
    {
        [DataContract]
        public class User
        {
            public User() { }
            public User(String JSONObject)
            {
                MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(JSONObject));
                DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(User));
    
                User user = (User)dataContractJsonSerializer.ReadObject(stream);
                this.ID = user.ID;
                this.Country = user.Country;
                this.FirstName = user.FirstName;
                this.LastName = user.LastName;
                this.Nickname = user.Nickname;
                this.PhoneNumber = user.PhoneNumber;
                this.DisplayPicture = user.DisplayPicture;
                this.IsRegistred = user.IsRegistred;
                this.IsConfirmed = user.IsConfirmed;
                this.VerificationCode = user.VerificationCode;
                this.Meetings = user.Meetings;
            }
    
            [DataMember(Name = "_id")]
            [JsonProperty(PropertyName = "_id")]
            public String ID { get; set; }
    
            [DataMember(Name = "country")]
            [JsonProperty(PropertyName = "country")]
            public String Country { get; set; }
    
            [DataMember(Name = "firstname")]
            [JsonProperty(PropertyName = "firstname")]
            public String FirstName { get; set; }
    
            [DataMember(Name = "lastname")]
            [JsonProperty(PropertyName = "lastname")]
            public String LastName { get; set; }
    
            [DataMember(Name = "nickname")]
            [JsonProperty(PropertyName = "nickname")]
            public String Nickname { get; set; }
    
            [DataMember(Name = "number")]
            [JsonProperty(PropertyName = "number")]
            public String PhoneNumber { get; set; }
    
            [DataMember(Name = "thumbnail")]
            [JsonProperty(PropertyName = "thumbnail")]
            public String DisplayPicture { get; set; }
    
            [DataMember(Name = "registered")]
            [JsonProperty(PropertyName = "registered")]
            public bool IsRegistred { get; set; }
    
            [DataMember(Name = "confirmed")]
            [JsonProperty(PropertyName = "confirmed")]
            public bool IsConfirmed { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "verification_code")]
            public String VerificationCode { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "meeting_ids")]
            public List<Meeting> Meetings { get; set; }
    
            public String toJSONString()
            {
                return JsonConvert.SerializeObject(this, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
            }
        }
    }

Espero que ayude ...

Ahmed Abulazm
fuente
1
Bravo Ahmed Abulazm. gracias, me salvó de mucho trabajo. :)
Sike12
2

Con referencia a la solución de @ ThoHo, usar el setter es en realidad todo lo que se necesita, sin etiquetas adicionales.

Para mí, anteriormente tenía un ID de referencia único, que quería cargar y agregar a la nueva colección de ID de referencia. Al cambiar la definición del Id de referencia para que solo contenga un método setter, que agregó el valor a la nueva colección. Json no puede volver a escribir el valor si la propiedad no tiene un get; método.

// Old property that I want to read from Json, but never write again. No getter.
public Guid RefId { set { RefIds.Add(value); } }

// New property that will be in use from now on. Both setter and getter.
public ICollection<Guid> RefIds { get; set; }

Esta clase ahora es compatible con la versión anterior y solo guarda los RefIds para las nuevas versiones.

simo.3792
fuente
1

Para aprovechar la respuesta de Tho Ho, esto también se puede usar para campos.

[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { IgnoreOnSerializing = value; } }

[JsonIgnore]
public string IgnoreOnSerializing;
Bendik August Nesbø
fuente
1

Dependiendo de en qué parte de la aplicación se lleve a cabo y si es solo una propiedad, una forma manual de hacerlo es estableciendo el valor de la propiedad en nulo y luego, en el modelo, puede especificar que la propiedad se ignore si el valor es nulo:

[JsonProperty(NullValueHandling = NullValue.Ignore)]
public string MyProperty { get; set; }

Si está trabajando en una aplicación web ASP.NET Core, puede configurarlo globalmente para todas las propiedades en todos los modelos estableciendo esto en su archivo Startup.cs:

public void ConfigureServices(IServiceCollection services) {
    // other configuration here
    services.AddMvc()
        .AddJsonOptions(options => options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore);
}
OzzyTheGiant
fuente
0

Si usa JsonConvert, IgnoreDataMemberAttribute está bien. Mi biblioteca estándar no hace referencia a Newton.Json, y uso [IgnoreDataMember] para controlar la serialización de objetos.

Del documento de ayuda de Newton.net .

menxin
fuente
0

¿Existe alguna forma sencilla de marcar (por ejemplo, con atributos) una propiedad de un objeto C #, de modo que Json.net lo ignore SOLAMENTE al serializar, pero lo atenderá al deserializar?

La forma más fácil que he encontrado al momento de escribir este artículo es incluir esta lógica en su IContractResolver .

Código de muestra del enlace anterior copiado aquí para la posteridad:

public class Employee
{
    public string Name { get; set; }
    public Employee Manager { get; set; }

    public bool ShouldSerializeManager()
    {
        // don't serialize the Manager property if an employee is their own manager
        return (Manager != this);
    }
}

public class ShouldSerializeContractResolver : DefaultContractResolver
{
    public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (property.DeclaringType == typeof(Employee) && property.PropertyName == "Manager")
        {
            property.ShouldSerialize =
                instance =>
                {
                    Employee e = (Employee)instance;
                    return e.Manager != e;
                };
        }

        return property;
    }
}

Todas las respuestas son buenas, pero este enfoque parecía ser el más limpio. De hecho, implementé esto buscando un atributo en la propiedad para SkipSerialize y SkipDeserialize para que pueda marcar cualquier clase que controle. ¡Gran pregunta!

Sin reembolsos Sin devoluciones
fuente