.NET NewtonSoft JSON deserialize map to a differen

2019-01-01 00:26发布

问题:

I have following JSON string which is received from an external party.

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

My mapping classes:

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

The question is that I don\'t like the \"Attribute class\" and the \"attributes field name\" in the Team class. Instead, I want it to be named as \"TeamScore\" and also to remove \"_\" from the field names and give proper names.

JsonConvert.DeserializeObject<RootObject>(jsonText);

I can change the \"Attribute\" class to a \"TeamScore\", but if I change the filed name (attributes in the Team Class), it won\'t deserialize properly and gives me null. How can I overcome this?

回答1:

Json.NET has a JsonPropertyAttribute which allows you to specify the name of a JSON property, so your code should be:

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: Serialization Attributes



回答2:

If you\'d like to use dynamic mapping, and don\'t want to clutter up your model with attributes, this approach worked for me

Usage:

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

Logic:

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


回答3:

Adding to Jacks solution. I need to Deserialize using the JsonProperty and Serialize while ignoring the JsonProperty (or vice versa). ReflectionHelper and Attribute Helper are just helper classes that get a list of properties or attributes for a property. I can include if anyone actually cares. Using the example below you can serialize the viewmodel and get \"Amount\" even though the JsonProperty is \"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);

Model:

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


回答4:

Expanding Rentering.com\'s answer, in scenarios where a whole graph of many types is to be taken care of, and you\'re looking for a strongly typed solution, this class can help, see usage (fluent) below. It operates as either a black-list or white-list per type. A type cannot be both (Gist - also contains global ignore list).

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