O JSON do .NET NewtonSoft desserializa o mapa para um nome de propriedade diferente

294

Eu tenho a seguinte sequência JSON que é recebida de uma parte externa.

{
   "team":[
      {
         "v1":"",
         "attributes":{
            "eighty_min_score":"",
            "home_or_away":"home",
            "score":"22",
            "team_id":"500"
         }
      },
      {
         "v1":"",
         "attributes":{
            "eighty_min_score":"",
            "home_or_away":"away",
            "score":"30",
            "team_id":"600"
         }
      }
   ]
}

Minhas aulas de mapeamento:

public class Attributes
{
    public string eighty_min_score { get; set; }
    public string home_or_away { get; set; }
    public string score { get; set; }
    public string team_id { get; set; }
}

public class Team
{
    public string v1 { get; set; }
    public Attributes attributes { get; set; }
}

public class RootObject
{
    public List<Team> team { get; set; }
}

A questão é que eu não gosto do Attributes nome da classe e dos attributes nomes dos campos na Teamclasse. Em vez disso, quero que ele seja nomeado TeamScoree também remova _dos nomes dos campos e forneça nomes próprios.

JsonConvert.DeserializeObject<RootObject>(jsonText);

Eu posso renomear Attributespara TeamScore, mas se eu alterar o nome do campo ( attributesna Teamclasse), ele não será desserializado corretamente e me fornecerá null. Como posso superar isso?

JenonD
fonte

Respostas:

572

O Json.NET possui um JsonPropertyAttributeque permite especificar o nome de uma propriedade JSON, portanto seu código deve ser:

public class TeamScore
{
    [JsonProperty("eighty_min_score")]
    public string EightyMinScore { get; set; }
    [JsonProperty("home_or_away")]
    public string HomeOrAway { get; set; }
    [JsonProperty("score ")]
    public string Score { get; set; }
    [JsonProperty("team_id")]
    public string TeamId { get; set; }
}

public class Team
{
    public string v1 { get; set; }
    [JsonProperty("attributes")]
    public TeamScore TeamScores { get; set; }
}

public class RootObject
{
    public List<Team> Team { get; set; }
}

Documentação: Atributos de serialização

outcoldman
fonte
2
posso usar dois JsonProperty para um arquivado?
Ali Yousefi
1
@AliYousefie Não pense assim. Mas a boa pergunta será: o que você espera obter disso?
Outcoldman
5
Eu tenho uma interface, duas classes são usadas nessa interface, mas os dados do servidor têm dois nomes de propriedades para duas classes, eu quero usar dois JsonProperty para uma propriedade em minhas interfaces.
Ali Yousefi
como podemos ter certeza de ter a resposta [objeto deserilized] têm valor para EightyMinScore e não eighty_min_score
Gaurravs
No meu caso, estou enviando o RootObject como resposta final, mas quando o leio como json na resposta final, eighty_min_score é mostrado com valor e não com EightyMinScore
Gaurravs
115

Se você deseja usar o mapeamento dinâmico e não deseja sobrecarregar seu modelo com atributos, essa abordagem funcionou para mim

Uso:

var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new CustomContractResolver();
this.DataContext = JsonConvert.DeserializeObject<CountResponse>(jsonString, settings);

Lógica:

public class CustomContractResolver : DefaultContractResolver
{
    private Dictionary<string, string> PropertyMappings { get; set; }

    public CustomContractResolver()
    {
        this.PropertyMappings = new Dictionary<string, string> 
        {
            {"Meta", "meta"},
            {"LastUpdated", "last_updated"},
            {"Disclaimer", "disclaimer"},
            {"License", "license"},
            {"CountResults", "results"},
            {"Term", "term"},
            {"Count", "count"},
        };
    }

    protected override string ResolvePropertyName(string propertyName)
    {
        string resolvedName = null;
        var resolved = this.PropertyMappings.TryGetValue(propertyName, out resolvedName);
        return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
    }
}
Jack
fonte
1
); Se simplificá-lo um pouco para o meu propósito, mas esta é uma solução melhor do que "a desordem se você modela / domínio"
Andreas
4
Uau. Isso é épico; maneira muito mais arquitetonicamente sólida de fazê-lo.
David Betz
1
Pode valer a pena (se você criar mais de um deles) mover o dicionário, pesquisar o código até uma classe base para todos os mapeamentos de propriedades e deixá-los adicionar propriedades, mas ignorar os detalhes de como o mapeamento acontece. Pode valer a pena adicionar isso ao próprio Json.Net.
James White
Essa deve ser a resposta aceitável porque, como disse o @DavidBetz, é o melhor design.
precisa saber é o seguinte
Esta solução também funciona com propriedades aninhadas? Tentei desserializar um objeto com propriedades aninhadas e ele não funciona.
Avi K.
8

Adicionando à solução de Jacks. Preciso desserializar usando o JsonProperty e Serialize enquanto ignoro o JsonProperty (ou vice-versa). ReflectionHelper e Attribute Helper são apenas classes auxiliares que obtêm uma lista de propriedades ou atributos para uma propriedade. Posso incluir se alguém realmente se importa. Usando o exemplo abaixo, você pode serializar o viewmodel e obter "Amount", mesmo que o JsonProperty seja "RecurringPrice".

    /// <summary>
    /// Ignore the Json Property attribute. This is usefule when you want to serialize or deserialize differently and not 
    /// let the JsonProperty control everything.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class IgnoreJsonPropertyResolver<T> : DefaultContractResolver
    {
        private Dictionary<string, string> PropertyMappings { get; set; }

        public IgnoreJsonPropertyResolver()
        {
            this.PropertyMappings = new Dictionary<string, string>();
            var properties = ReflectionHelper<T>.GetGetProperties(false)();
            foreach (var propertyInfo in properties)
            {
                var jsonProperty = AttributeHelper.GetAttribute<JsonPropertyAttribute>(propertyInfo);
                if (jsonProperty != null)
                {
                    PropertyMappings.Add(jsonProperty.PropertyName, propertyInfo.Name);
                }
            }
        }

        protected override string ResolvePropertyName(string propertyName)
        {
            string resolvedName = null;
            var resolved = this.PropertyMappings.TryGetValue(propertyName, out resolvedName);
            return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
        }
    }

Uso:

        var settings = new JsonSerializerSettings();
        settings.DateFormatString = "YYYY-MM-DD";
        settings.ContractResolver = new IgnoreJsonPropertyResolver<PlanViewModel>();
        var model = new PlanViewModel() {Amount = 100};
        var strModel = JsonConvert.SerializeObject(model,settings);

Modelo:

public class PlanViewModel
{

    /// <summary>
    ///     The customer is charged an amount over an interval for the subscription.
    /// </summary>
    [JsonProperty(PropertyName = "RecurringPrice")]
    public double Amount { get; set; }

    /// <summary>
    ///     Indicates the number of intervals between each billing. If interval=2, the customer would be billed every two
    ///     months or years depending on the value for interval_unit.
    /// </summary>
    public int Interval { get; set; } = 1;

    /// <summary>
    ///     Number of free trial days that can be granted when a customer is subscribed to this plan.
    /// </summary>
    public int TrialPeriod { get; set; } = 30;

    /// <summary>
    /// This indicates a one-time fee charged upfront while creating a subscription for this plan.
    /// </summary>
    [JsonProperty(PropertyName = "SetupFee")]
    public double SetupAmount { get; set; } = 0;


    /// <summary>
    /// String representing the type id, usually a lookup value, for the record.
    /// </summary>
    [JsonProperty(PropertyName = "TypeId")]
    public string Type { get; set; }

    /// <summary>
    /// Billing Frequency
    /// </summary>
    [JsonProperty(PropertyName = "BillingFrequency")]
    public string Period { get; set; }


    /// <summary>
    /// String representing the type id, usually a lookup value, for the record.
    /// </summary>
    [JsonProperty(PropertyName = "PlanUseType")]
    public string Purpose { get; set; }
}
Rentering.com
fonte
2
Obrigado pelo seu IgnoreJsonPropertyResolver, já que eu estava procurando fazer a mesma coisa (ignore o JsonProperty apenas na serialização). Infelizmente, sua solução funciona apenas para atributos de nível superior e não para tipos aninhados. A maneira correta de ignorar todos os atributos JsonProperty ao serializar é substituir CreatePropertyno ContractResolver. Aí chama a base: var jsonProperty = base.CreateProperty(memberInfo, memberSerialization);e depois define jsonProperty.PropertyName = memberInfo.Name;. Finalmente, return jsonProperty;é tudo o que você precisa.
Nate Cook
1
o que são esses ajudantes?
deadManN
1
@NateCook, você pode me mostrar uma amostra? eu preciso que ele mal agora
deadManN
4

Expandindo a resposta da Rentering.com , em cenários em que é necessário cuidar de um gráfico inteiro de muitos tipos e você está procurando uma solução fortemente tipada, esta classe pode ajudar a ver o uso (fluente) abaixo. Opera como lista negra ou lista branca por tipo. Um tipo não pode ser ambos ( Gist - também contém lista de ignorados global).

public class PropertyFilterResolver : DefaultContractResolver
{
  const string _Err = "A type can be either in the include list or the ignore list.";
  Dictionary<Type, IEnumerable<string>> _IgnorePropertiesMap = new Dictionary<Type, IEnumerable<string>>();
  Dictionary<Type, IEnumerable<string>> _IncludePropertiesMap = new Dictionary<Type, IEnumerable<string>>();
  public PropertyFilterResolver SetIgnoredProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
  {
    if (propertyAccessors == null) return this;

    if (_IncludePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);

    var properties = propertyAccessors.Select(GetPropertyName);
    _IgnorePropertiesMap[typeof(T)] = properties.ToArray();
    return this;
  }

  public PropertyFilterResolver SetIncludedProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
  {
    if (propertyAccessors == null)
      return this;

    if (_IgnorePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);

    var properties = propertyAccessors.Select(GetPropertyName);
    _IncludePropertiesMap[typeof(T)] = properties.ToArray();
    return this;
  }

  protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
  {
    var properties = base.CreateProperties(type, memberSerialization);

    var isIgnoreList = _IgnorePropertiesMap.TryGetValue(type, out IEnumerable<string> map);
    if (!isIgnoreList && !_IncludePropertiesMap.TryGetValue(type, out map))
      return properties;

    Func<JsonProperty, bool> predicate = jp => map.Contains(jp.PropertyName) == !isIgnoreList;
    return properties.Where(predicate).ToArray();
  }

  string GetPropertyName<TSource, TProperty>(
  Expression<Func<TSource, TProperty>> propertyLambda)
  {
    if (!(propertyLambda.Body is MemberExpression member))
      throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property.");

    if (!(member.Member is PropertyInfo propInfo))
      throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property.");

    var type = typeof(TSource);
    if (!type.GetTypeInfo().IsAssignableFrom(propInfo.DeclaringType.GetTypeInfo()))
      throw new ArgumentException($"Expresion '{propertyLambda}' refers to a property that is not from type '{type}'.");

    return propInfo.Name;
  }
}

Uso:

var resolver = new PropertyFilterResolver()
  .SetIncludedProperties<User>(
    u => u.Id, 
    u => u.UnitId)
  .SetIgnoredProperties<Person>(
    r => r.Responders)
  .SetIncludedProperties<Blog>(
    b => b.Id)
  .Ignore(nameof(IChangeTracking.IsChanged)); //see gist
Shimmy Weitzhandler
fonte
0

Estou usando atributos JsonProperty ao serializar, mas ignorando-os ao desserializar usando isso ContractResolver:

public class IgnoreJsonPropertyContractResolver: DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var properties = base.CreateProperties(type, memberSerialization);
            foreach (var p in properties) { p.PropertyName = p.UnderlyingName; }
            return properties;
        }
    }

O ContractResolverjust retorna todas as propriedades ao nome da propriedade da classe (simplificado da solução de Shimmy). Uso:

var airplane= JsonConvert.DeserializeObject<Airplane>(json, 
    new JsonSerializerSettings { ContractResolver = new IgnoreJsonPropertyContractResolver() });
Jovie
fonte