.NET NewtonSoft JSON反序列化映射到不同的属性名

367

我有以下来自外部方的JSON字符串。

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

我的映射类:

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

问题在于我不喜欢Team类中的Attributes 类名attributes 字段名称。相反,我希望它被命名为TeamScore,并且删除字段名称中的_并给出适当的名称。

JsonConvert.DeserializeObject<RootObject>(jsonText);

我可以将Attributes重命名为TeamScore,但如果我更改字段名称(Team类中的attributes),它将无法正确反序列化并给出null。我该如何克服这个问题?


6个回答

722

Json.NET - Newtonsoft有一个JsonPropertyAttribute,可以让您指定JSON属性的名称,因此您的代码应该如下:

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

文档:序列化属性


5
一个字段可以使用两个JsonProperty吗? - Ali Yousefi
2
@AliYousefie 不这么认为。但是一个好问题是,你期望从中获得什么? - outcoldman
6
我有一个接口,两个类使用了这个接口,但是服务器数据有两个属性名称对应于这两个类,我想在我的接口中为一个属性使用两个JsonProperty。 - Ali Yousefi
1
我需要从API读取数据并对其进行反序列化以便于应用到我的模型中。然后将该模型作为操作结果发送。是否有一种方法可以将属性(比如 foo_bar)的数据进行反序列化,但是序列化成另一个名称(比如 foo)呢? - Hamid Mayeli
2
@CamilleSévigny FYI,它们都是同一个东西。NewtonSoft以前被称为Json.NET,这个库的作者名字叫“James Newton-King”,因此取名为NewtonSoft。请按照答案开头outcoldman提供的链接,检查他们的github项目,该链接指向:https://github.com/JamesNK/Newtonsoft.Json - Khurram Hassan
显示剩余3条评论

153

如果您想使用动态映射,而不想在模型中添加属性使其混乱,这种方法适用于我。

用法:

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

逻辑:

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
我稍微简化了一下,但这比“混乱你的模型/域”更好的解决方案;) - Andreas
8
哇,太棒了!这种做法在建筑上更加合理。 - David Betz
1
如果你创建了多个这样的对象,将字典和查找代码移动到所有属性映射的基类中,并让它们添加属性但忽略映射发生的细节可能是值得的。也许将其添加到Json.Net本身中会更好。 - James White
有没有类似上面的方法可以忽略属性? - Shimmy Weitzhandler
1
@ChristopherDunn - 我已经成功地解决了这个问题,而不需要进行任何操作 - 在我需要在反序列化时对某些 DTO 进行灵活处理时,我的 DTO 中没有任何 json.net 属性。 - IbrarMumtaz
显示剩余5条评论

9

在Jack的解决方案基础上,我需要使用JsonProperty进行反序列化,同时忽略JsonProperty进行序列化(或者反之亦然)。ReflectionHelper和Attribute Helper只是帮助获取属性或属性的属性列表的辅助类。如果有人真正关心,我可以包含它们。使用下面的示例,即使JsonProperty为“RecurringPrice”,您也可以将视图模型序列化并获取“Amount”。

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

使用方法:

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

模型:

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

2
感谢你的IgnoreJsonPropertyResolver,因为我也在寻找做同样的事情(只在序列化时忽略JsonProperty)。不幸的是,你的解决方案只适用于顶级属性而不是嵌套类型。忽略所有JsonProperty属性进行序列化的正确方法是在ContractResolver中重写CreateProperty。在那里调用基类:var jsonProperty = base.CreateProperty(memberInfo, memberSerialization);,然后设置 jsonProperty.PropertyName = memberInfo.Name;。最后return jsonProperty;这就是你需要的全部内容。 - Nate Cook
1
这些 helpers 是什么? - Hassan Faghihi
1
@NateCook,你能给我展示一个样例吗?我现在非常需要它。 - Hassan Faghihi

6

扩展Rentering.com的回答,在需要处理多种类型的整个图形场景中,如果你正在寻找一个强类型的解决方案,那么这个类可以帮助你,见下面的用法(流畅)。它可作为黑名单或白名单,每种类型只能是一种。类型不能同时属于两个列表(Gist–还包含全局忽略列表)。

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

使用方法:

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

我在序列化时使用了JsonProperty属性,但在使用此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;
        }
    }

ContractResolver 只是将每个属性设置回类属性名称(从Shimmy的解决方案简化而来)。用法:

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

2

如果你想忽略某些内容,可以使用这个方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接