.NET NewtonSoft JSON désérialise la carte en un nom de propriété différent

294

J'ai la chaîne JSON suivante qui est reçue d'une partie externe.

{
   "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"
         }
      }
   ]
}

Mes cours de cartographie:

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

La question est que je n'aime pas le Attributes nom de classe et les attributes noms de champs dans la Teamclasse. Au lieu de cela, je veux qu'il soit nommé TeamScoreet également supprimé _des noms de champs et donne les noms appropriés.

JsonConvert.DeserializeObject<RootObject>(jsonText);

Je peux renommer Attributesà TeamScore, mais si je change le nom du champ ( attributesdans la Teamclasse), il ne sera pas désérialiser correctement et me donne null. Comment puis-je surmonter cela?

JenonD
la source

Réponses:

572

Json.NET a un JsonPropertyAttributequi vous permet de spécifier le nom d'une propriété JSON, donc votre code doit être:

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

Documentation: Attributs de sérialisation

outcoldman
la source
2
puis-je utiliser deux JsonProperty pour un déposé?
Ali Yousefi
1
@AliYousefie Ne pense pas. Mais la bonne question sera, qu'espérez-vous en retirer?
outcoldman
5
j'ai une interface, deux classes sont utilisées cette interface, mais les données du serveur ont deux noms de propriété pour deux classes, je veux utiliser deux JsonProperty pour une propriété dans mes interfaces.
Ali Yousefi
comment pouvons-nous nous assurer que la réponse [objet déserilisé] a une valeur pour EightyMinScore et non eighty_min_score
Gaurravs
Dans mon cas, j'envoie le RootObject en tant que réponse finale, mais lorsque je le lis en tant que json dans la réponse finale, eighty_min_score est affiché avec une valeur et non avec EightyMinScore
Gaurravs
115

Si vous souhaitez utiliser le mappage dynamique et que vous ne voulez pas encombrer votre modèle avec des attributs, cette approche a fonctionné pour moi

Usage:

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

Logique:

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
la source
1
Cela a simplifié un peu mon objectif, mais c'est une meilleure solution que "encombrer votre modèle / domaine";)
Andreas
4
Sensationnel. C'est épique; manière beaucoup plus architecturale de le faire.
David Betz
1
Il peut être utile (si vous en créez plusieurs) de déplacer le dictionnaire, de rechercher du code dans une classe de base pour tous les mappages de propriétés et de les laisser ajouter des propriétés, mais d'ignorer les détails de la façon dont le mappage se produit. Cela pourrait valoir la peine d'ajouter cela à Json.Net lui-même.
James White
Cela devrait être la réponse acceptable car, comme l'a dit @DavidBetz, c'est le meilleur design.
im1dermike
Cette solution fonctionne-t-elle également avec les propriétés imbriquées? J'ai essayé de désérialiser un objet avec des propriétés imbriquées et cela ne fonctionne pas.
Avi K.
8

Ajout à la solution Jacks. J'ai besoin de désérialiser en utilisant JsonProperty et Serialize tout en ignorant JsonProperty (ou vice versa). ReflectionHelper et Attribute Helper ne sont que des classes d'assistance qui obtiennent une liste de propriétés ou d'attributs pour une propriété. Je peux inclure si quelqu'un s'en soucie réellement. En utilisant l'exemple ci-dessous, vous pouvez sérialiser le viewmodel et obtenir "Amount" même si le JsonProperty est "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);
        }
    }

Usage:

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

Modèle:

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
la source
2
Merci pour votre IgnoreJsonPropertyResolver, car je cherchais à faire la même chose (ignorer JsonProperty sur la sérialisation uniquement). Malheureusement, votre solution ne fonctionne que pour les attributs de niveau supérieur et non pour les types imbriqués. La bonne façon d'ignorer tous les attributs JsonProperty lors de la sérialisation consiste à remplacer CreatePropertydans ContractResolver. Là, appelez la base: var jsonProperty = base.CreateProperty(memberInfo, memberSerialization);puis définissez jsonProperty.PropertyName = memberInfo.Name;. Enfin, return jsonProperty;c'est tout ce dont vous avez besoin.
Nate Cook
1
quels sont ces aides?
deadManN
1
@NateCook pouvez-vous me montrer un échantillon?
j'en ai vraiment
4

En développant la réponse de Rentering.com , dans les scénarios où un graphique entier de nombreux types doit être pris en charge et que vous recherchez une solution fortement typée, cette classe peut vous aider, voir utilisation (fluide) ci-dessous. Il fonctionne comme une liste noire ou une liste blanche par type. Un type ne peut pas être les deux ( Gist - contient également une liste globale d'ignorance).

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

Usage:

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
la source
0

J'utilise les attributs JsonProperty lors de la sérialisation mais je les ignore lors de la désérialisation en utilisant ceci 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;
        }
    }

Le ContractResolverdéfinit simplement chaque propriété sur le nom de la propriété de classe (simplifié à partir de la solution de Shimmy). Usage:

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