Deserializar uma propriedade, mas não serializar com json.net

124

Temos alguns arquivos de configuração que foram gerados serializando objetos C # com Json.net.

Gostaríamos de migrar uma propriedade da classe serializada de uma propriedade de enum simples para uma propriedade de classe.

Uma maneira fácil de fazer isso seria deixar a propriedade enum antiga na classe e fazer com que o Json.net leia essa propriedade quando carregarmos a configuração, mas não salvá-la novamente na próxima serialização do objeto. Lidaremos com a geração da nova classe do enum antigo separadamente.

Existe alguma maneira simples de marcar (por exemplo, com atributos) uma propriedade de um objeto C #, de modo que Json.net irá ignorá-lo SOMENTE ao serializar, mas prestar atenção a ele ao desserializar?

Will Dean
fonte
Que tal um conversor customizado: você pode usá-lo como um atributo em sua propriedade, substituir ReadJson e WriteJson com comportamentos diferentes, não? Exemplo (não exatamente o que você precisa, mas ...) weblogs.asp.net/thangchung/archive/2010/08/26/…
Raphaël Althaus
O atributo OnDeserialized pode ser uma
solução alternativa
Isso não deveria ser possível usando o atributo `[JsonIgnore] '?? james.newtonking.com/archive/2009/10/23/…
Juri
Você consegue expandir como isso pode ser usado em apenas uma direção, conforme o último parágrafo do q?
Will Dean,
É possível usar [JsonIgnore] em combinação com um configurador privado secundário decorado com um atributo [JsonProperty]. Existem também algumas outras soluções simples. Eu adicionei um artigo detalhado.
Brian Rogers

Respostas:

133

Na verdade, existem várias abordagens bastante simples que você pode usar para alcançar o resultado desejado.

Vamos supor, por exemplo, que você tem suas classes atualmente definidas assim:

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

enum Fizz { Alpha, Beta, Gamma }

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

E você quer fazer isso:

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 esta:

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

Abordagem 1: Adicionar um método ShouldSerialize

Json.NET tem a capacidade de serializar propriedades condicionalmente, procurando por ShouldSerializemétodos correspondentes na classe.

Para usar este recurso, adicione um ShouldSerializeBlah()método booleano à sua classe, onde Blahé substituído pelo nome da propriedade que você não deseja serializar. Faça a implementação deste método sempre retornar false.

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

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

Observação: se você gosta dessa abordagem, mas não quer complicar a interface pública de sua classe com a introdução de um ShouldSerializemétodo, pode usar um IContractResolverpara fazer a mesma coisa programaticamente. Consulte Serialização condicional de propriedade na documentação.

Abordagem 2: Manipular o JSON com JObjects

Em vez de usar JsonConvert.SerializeObjectpara fazer a serialização, carregue o objeto de configuração em um JObjecte simplesmente remova a propriedade indesejada do JSON antes de escrevê-lo. São apenas algumas linhas extras de código.

JObject jo = JObject.FromObject(config);

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

json = jo.ToString();

Abordagem 3: uso inteligente (ab) de atributos

  1. Aplique um [JsonIgnore]atributo à propriedade que você não deseja que seja serializada.
  2. Adicione um configurador de propriedade privado alternativo à classe com o mesmo tipo da propriedade original. Faça a implementação dessa propriedade definir a propriedade original.
  3. Aplique um [JsonProperty]atributo ao configurador alternativo, dando a ele o mesmo nome JSON da propriedade original.

Aqui está a Configaula 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
fonte
7
Resolvemos isso em nosso projeto (que usa um superconjunto interno específico de integração do modelo base, onde nenhuma das propriedades da superclasse deve ser serializada), definindo as propriedades get para interno. Ter configuradores públicos permitia que a API Web definisse as propriedades, mas impedia que ela as serializasse.
Daniel Saidi
7
Em conjunto com o uso de JsonPropertyAttribute, a partir do C # 6.0, você pode usar a nameofpalavra - chave em vez de usar "strings mágicas". Isso torna a refatoração muito mais fácil e à prova de idiotas - além disso, se você deixar de renomear qualquer ocorrência, o compilador irá avisá-lo de qualquer maneira. Usando o exemplo de @Brian, o uso seria este:[JsonProperty(nameof(ObsoleteSetting))]
Geoff James
1
É uma má ideia usar nameof () em declarações JsonProperty, particularmente neste cenário legado. O JSON representa um contrato externo (e provavelmente eterno) com outra interface e você definitivamente NÃO deseja alterar o nome da propriedade JSON se refatorar. Você estaria quebrando a compatibilidade com todos os arquivos JSON existentes e componentes que geram JSON neste formato. Na verdade, seria melhor colocar JsonProperty (…) com o nome completo em cada propriedade serializada para garantir que não mudem se você renomear a propriedade posteriormente.
Ammo Goettsch
36

Para qualquer situação em que seja aceitável ter sua propriedade somente de desserialização marcada como interna, há uma solução incrivelmente simples que não depende de atributos. Basta marcar a propriedade como get interno, mas definir público:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

Isso resulta na desserialização correta usando configurações / resolvedores padrão / etc., Mas a propriedade é removida da saída serializada.

Iucounu
fonte
Solução simples, mas inteligente.
hbulens
Observe que a propriedade também será ignorada pelo módulo de validação. (Portanto, você não pode mais marcá-lo como [Obrigatório] para desserialização, pois isso depende de um getmétodo público ).
Martin Hansen,
2
Isso não funciona com internalnem private. É sempre serializado.
Paulo
Isto não funcionou para mim. Obteve um erro de propriedade não encontrada ao desserializar.
Drew Sumido
31

Eu gosto de manter os atributos neste aqui, aqui está o método que uso quando preciso desserializar uma propriedade, mas não serializá-la ou vice-versa.

PASSO 1 - Crie o atributo personalizado

public class JsonIgnoreSerializationAttribute : Attribute { }

ETAPA 2 - Crie um Reslover de contrato personalizado

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

ETAPA 3 - Adicionar atributo onde a serialização não é necessária, mas a desserialização é

    [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

PASSO 4 - Use-o

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

Espero que isto ajude! Também é importante notar que isso também irá ignorar as propriedades quando ocorrer a desserialização, quando estou deserializando, utilizo apenas o conversor da forma convencional.

JsonConvert.DeserializeObject<MyType>(myString);
Jraco11
fonte
Obrigado por esta implementação útil. Existe uma maneira de estender a implementação básica em GetSerializableMembersvez de substituí-la inteiramente?
Alain
4
Esquece, acabei de perceber que era tão simples como:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Alain
não sei por que essa não é a resposta com melhor classificação. é limpo, segue os padrões da newtonsoft e é fácil de fazer. a única coisa que gostaria de acrescentar é que você pode configurá-lo globalmente usandoJsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Matt M
1
esqueça, isso não faz realmente o que o autor da pergunta está pedindo. isso é basicamente recriar o JsonIgnore. ele não ignora a propriedade para serialização, mas define a propriedade durante a desserialização porque não há como o método GetSerializableMembers saber se é uma leitura ou gravação, então você está excluindo as propriedades de ambos. esta solução não funciona.
Matt M
7

Use a propriedade 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 esta ajuda.

Tho Ho
fonte
1
Obrigado. Observe que a JsonProperty deve ter uma letra maiúscula IgnoreOnSerializing, igual à propriedade. Eu recomendo usar nameof(IgnoreOnSerializing)para evitar string mágica, em caso de renomeação.
Bendik August Nesbø
5

Depois de passar um longo tempo procurando como sinalizar uma propriedade de classe como De-Serializable e NÃO Serializable, descobri que não existe tal coisa para fazer isso; então eu vim com uma solução que combina duas bibliotecas ou técnicas de serialização diferentes (System.Runtime.Serialization.Json & Newtonsoft.Json) e funcionou para mim da seguinte forma:

  • sinalize todas as suas classes e subclasses como "DataContract".
  • sinalize todas as propriedades de sua classe e subclasses como "DataMember".
  • sinalize todas as propriedades de sua classe e subclasses como "JsonProperty", exceto aquelas que você deseja que não sejam serializadas.
  • agora sinalize as propriedades que você NÃO deseja que sejam serializadas como "JsonIgnore".
  • em seguida, serialize usando "Newtonsoft.Json.JsonConvert.SerializeObject" e 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 ajude ...

Ahmed Abulazm
fonte
1
Bravo Ahmed Abulazm. obrigado, isso me salvou de muito trabalho. :)
Sike12
2

com referência à solução de @ThoHo, usar o setter é na verdade tudo o que é necessário, sem tags adicionais.

Para mim, anteriormente, eu tinha um único ID de referência, que queria carregar e adicionar à nova coleção de IDs de referência. Alterando a definição do Id de referência para conter apenas um método setter, que agregou o valor à nova coleção. Json não pode escrever o valor de volta se a propriedade não tiver um 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 classe agora é compatível com a versão anterior e salva apenas os RefIds para as novas versões.

simo.3792
fonte
1

Para desenvolver a resposta de Tho Ho, isso também pode ser usado para campos.

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

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

Dependendo de onde isso ocorre no aplicativo e se for apenas uma propriedade, uma maneira manual de fazer isso é definindo o valor da propriedade como nulo e, em seguida, no modelo, você pode especificar que a propriedade seja ignorada se o valor for nulo:

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

Se você estiver trabalhando em um aplicativo da web ASP.NET Core, poderá definir isso globalmente para todas as propriedades em todos os modelos, definindo isso em seu arquivo Startup.cs:

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

Se você usar JsonConvert, IgnoreDataMemberAttribute está ok.Minha biblioteca padrão não refrence Newton.Json, e eu uso [IgnoreDataMember] para controlar a serialização de objetos.

Do documento de ajuda Newton.net .

menxina
fonte
0

Existe alguma maneira simples de marcar (por exemplo, com atributos) uma propriedade de um objeto C #, de modo que Json.net irá ignorá-lo SOMENTE ao serializar, mas prestar atenção a ele ao desserializar?

A maneira mais fácil que encontrei até o momento desta escrita é incluir essa lógica em seu IContractResolver .

Exemplo de código do link acima copiado aqui para a posteridade:

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 as respostas são boas, mas essa abordagem parecia a maneira mais limpa. Na verdade, eu implementei isso procurando um atributo na propriedade para SkipSerialize e SkipDeserialize para que você possa apenas marcar qualquer classe que você controlar. Ótima pergunta!

Sem Reembolsos Sem Devoluções
fonte