369

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 Attributes class name and the attributes field names in the Team class. Instead, I want it to be named TeamScore and also to remove _ from the field names and give proper names.

JsonConvert.DeserializeObject<RootObject>(jsonText);

I can rename Attributes to TeamScore, but if I change the field name (attributes in the Team class), it won't deserialize properly and gives me null. How can I overcome this?

1

6 Answers 6

724

Json.NET - Newtonsoft 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

8
  • 5
    can i use two JsonProperty for one filed? May 11, 2015 at 19:56
  • 2
    @AliYousefie Don't think so. But the good question will be, what do you expect to get from that?
    – outcoldman
    Jul 6, 2015 at 20:02
  • 6
    i have an interface, two classes are used this interface,but server data have two property name for two classes, i want use two JsonProperty for one property in my interfaces. Apr 7, 2016 at 6:35
  • 1
    I need to read data from an API and deserialize it to my model. Then send that model as the result of my action. Is there any way to say deserialize data from a property (Let's say foo_bar) but serialize to another name (Let's say foo). Feb 13, 2020 at 21:40
  • 2
    @CamilleSévigny FYI they both are the same. NewtonSoft was formerly known as Json.NET. The library's author's name is "James Newton-King", hence NewtonSoft. Follow the link provided by outcoldman at the start of the answer and check their github project, it points to: github.com/JamesNK/Newtonsoft.Json Nov 8, 2021 at 2:53
155

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);
    }
}
10
  • 3
    Did simplify it a bit for my purpose but this is a better solution then "clutter up you model/domain" ;)
    – Andreas
    Jul 30, 2015 at 8:44
  • 8
    Wow. That's epic; much more architecturally sound manner of doing it.
    – David Betz
    Dec 11, 2015 at 2:53
  • 1
    It might (if you create more than one of these) be worth moving the dictionary, lookup code up to a base class for all property mappings and let them add properties but ignore the details of how the mapping happens. Might be worth just adding that to Json.Net itself. Dec 30, 2015 at 14:32
  • This should be the acceptable answer because, as @DavidBetz said, it's the best design.
    – mellis481
    Jan 29, 2016 at 14:46
  • 1
    @ChristopherDunn - i got this working without having to do this - there are no json.net attrs on any of my DTOs that i needed some flexibility around when deserializing. Oct 29, 2019 at 14:27
9

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; }
}
3
  • 2
    Thanks for your IgnoreJsonPropertyResolver, since I was looking to do the same thing (ignore JsonProperty on serialization only). Unfortunately your solution only works for top level attributes and not nested types. The proper way to ignore all JsonProperty attributes when serializing is to override CreateProperty in the ContractResolver. There call the base: var jsonProperty = base.CreateProperty(memberInfo, memberSerialization); and then set jsonProperty.PropertyName = memberInfo.Name;. Finally return jsonProperty; That's all you need.
    – Nate Cook
    Dec 18, 2015 at 18:40
  • 1
    what are these helpers? Jul 24, 2016 at 13:11
  • 1
    @NateCook can you show me a sample? i need it badly right now Jul 24, 2016 at 13:14
6

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
5

I am using JsonProperty attributes when serializing but ignoring them when deserializing using this 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;
        }
    }

The ContractResolver just sets every property back to the class property name (simplified from Shimmy's solution). Usage:

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

Also if you want to ignore something use this

  [JsonIgnore]
  public int Id { get; set; }
  [JsonProperty("id")]
  Public string real_id { get; set; }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.