.NET NewtonSoft JSON deserializa el mapa a un nombre de propiedad diferente

294

Tengo la siguiente cadena JSON que se recibe de una 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"
         }
      }
   ]
}

Mis clases de mapeo:

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 pregunta es que no me gusta el Attributes nombre de la clase y los attributes nombres de campo en la Teamclase. En cambio, quiero que se nombre TeamScorey también que se elimine _de los nombres de campo y se den los nombres propios.

JsonConvert.DeserializeObject<RootObject>(jsonText);

Puedo cambiar el nombre Attributesa TeamScore, pero si cambio el nombre del campo ( attributesen la Teamclase), no se deserializará correctamente y me dará null. ¿Cómo puedo superar esto?

JenonD
fuente

Respuestas:

572

Json.NET tiene un JsonPropertyAttributeque le permite especificar el nombre de una propiedad JSON, por lo que su código debe 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; }
}

Documentación: Atributos de serialización

outcoldman
fuente
2
¿Puedo usar dos JsonProperty para uno archivado?
Ali Yousefi
1
@AliYousefie No lo creo. Pero la buena pregunta será, ¿qué esperas obtener de eso?
outcoldman
55
Tengo una interfaz, se usan dos clases en esta interfaz, pero los datos del servidor tienen dos nombres de propiedad para dos clases, quiero usar dos JsonProperty para una propiedad en mis interfaces.
Ali Yousefi
¿Cómo podemos asegurarnos de que la respuesta [objeto deserilizado] tenga valor para EightyMinScore y no eighty_min_score
Gaurravs
En mi caso, estoy enviando el RootObject como respuesta final, pero cuando lo leo como json de la respuesta final, eighty_min_score se muestra con valor y no con EightyMinScore
Gaurravs
115

Si desea utilizar el mapeo dinámico, y no quiere saturar su modelo con atributos, este enfoque funcionó para mí

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
fuente
1
Lo simplifiqué un poco para mi propósito, pero esta es una solución mejor que "desordenar tu modelo / dominio";)
Andreas
44
Guau. Eso es épico; una manera mucho más arquitectónica de hacerlo.
David Betz
1
Puede que (si crea más de uno de estos) valga la pena mover el diccionario, busque el código en una clase base para todas las asignaciones de propiedades y permita que agreguen propiedades pero ignoren los detalles de cómo ocurre la asignación. Podría valer la pena agregar eso a Json.Net en sí.
James White
Esta debería ser la respuesta aceptable porque, como dijo @DavidBetz, es el mejor diseño.
im1dermike
¿Esta solución también funciona con propiedades anidadas? Intenté deserializar un objeto con propiedades anidadas y no funciona.
Avi K.
8

Agregando a la solución Jacks. Necesito deserializar usando JsonProperty y Serialize mientras ignoro JsonProperty (o viceversa). ReflectionHelper y Attribute Helper son solo clases de ayuda que obtienen una lista de propiedades o atributos para una propiedad. Puedo incluir si a alguien realmente le importa. Usando el siguiente ejemplo, puede serializar el modelo de vista y obtener "Cantidad" aunque la Propiedad Json sea "Precio recurrente".

    /// <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
fuente
2
Gracias por su IgnoreJsonPropertyResolver, ya que estaba buscando hacer lo mismo (ignore JsonProperty solo en serialización). Desafortunadamente, su solución solo funciona para atributos de nivel superior y no para tipos anidados. La forma correcta de ignorar todos los atributos de JsonProperty al serializar es anular CreatePropertyen ContractResolver. Llama a la base: var jsonProperty = base.CreateProperty(memberInfo, memberSerialization);y luego establece jsonProperty.PropertyName = memberInfo.Name;. Finalmente return jsonProperty;eso es todo lo que necesitas.
Nate Cook
1
¿Qué son estos ayudantes?
deadManN
1
@NateCook, ¿puedes mostrarme una muestra? lo necesito mucho en este momento
deadManN
4

Ampliando la respuesta de Rentering.com , en escenarios en los que se debe ocupar un gráfico completo de muchos tipos, y está buscando una solución fuertemente tipada, esta clase puede ayudar, vea el uso (fluido) a continuación. Funciona como una lista negra o una lista blanca por tipo. Un tipo no puede ser ambos ( Gist : también contiene una lista global de ignorados).

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
fuente
0

Estoy usando los atributos JsonProperty cuando serializo pero los ignoro cuando deserializo usando esto 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;
        }
    }

El ContractResolversimplemente se pone cada vuelta propiedad a nombre de la propiedad de clase (simplificado de la solución de Shimmy). Uso:

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