【问题标题】:Automatically bind pascal case c# model from snake case JSON in WebApi从 WebApi 中的蛇案例 JSON 自动绑定帕斯卡案例 c# 模型
【发布时间】:2019-06-28 22:07:10
【问题描述】:

我正在尝试将我的 PascalCased c# 模型与 WebApi v2 中的 snake_cased JSON 绑定(完整框架,而不是 dot net core)。

这是我的 api:

public class MyApi : ApiController
{
    [HttpPost]
    public IHttpActionResult DoSomething([FromBody]InputObjectDTO inputObject)
    {
        database.InsertData(inputObject.FullName, inputObject.TotalPrice)
        return Ok();
    }
}

这是我的输入对象:

public class InputObjectDTO
{
    public string FullName { get; set; }
    public int TotalPrice { get; set; }
    ...
}

我遇到的问题是 JSON 看起来像这样:

{
    "full_name": "John Smith",
    "total_price": "20.00"
}

我知道我可以使用 JsonProperty 属性:

public class InputObjectDTO
{
    [JsonProperty(PropertyName = "full_name")]
    public string FullName { get; set; }

    [JsonProperty(PropertyName = "total_price")]
    public int TotalPrice { get; set; }
}

但是我的 InputObjectDTO 巨大,而且还有很多其他人喜欢它。它有数百个属性都是蛇形的,最好不必为每个属性指定 JsonProperty 属性。我可以让它“自动”工作吗?也许使用自定义模型绑定器或自定义 json 转换器?

【问题讨论】:

  • 你无法控制创建 json 的 api?
  • 不,无法控制调用 api。
  • json字总是下划线隔开?
  • 对于这个问题,我们可以安全地假设我们可以简单地将下划线转换为大写的下一个字母(以及第一个字母)。
  • @Rocklan,你检查过stackoverflow.com/a/28553455/989462 吗?

标签: c# asp.net json.net asp.net-web-api2


【解决方案1】:

无需重新发明轮子。 Json.Net 已经有一个 SnakeCaseNamingStrategy 类来做你想做的事。您只需通过设置将其设置为DefaultContractResolver 上的NamingStrategy

将此行添加到 WebApiConfig 类中的 Register 方法中:

config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
    new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() };

这是一个演示(控制台应用程序)来证明这个概念:https://dotnetfiddle.net/v5siz7


如果您想将蛇形外壳应用于某些类而不是其他类,您可以通过应用[JsonObject] 属性来指定命名策略,如下所示:

[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
public class InputObjectDTO
{
    public string FullName { get; set; }
    public decimal TotalPrice { get; set; }
}

通过属性设置的命名策略优先于通过解析器设置的命名策略,因此您可以在解析器中设置默认策略,然后在需要时使用属性覆盖它。 (Json.Net 包含三种命名策略:SnakeCaseNamingStrategyCamelCaseNamingStrategyDefaultNamingStrategy。)


现在,如果您想反序列化使用一种命名策略,而序列化对同一类使用不同的策略,那么上述解决方案都不起作用对你来说,因为命名策略将在 Web API 中双向应用。因此,在这种情况下,您将需要像 @icepickle 的 answer 中所示的自定义内容来控制每个应用的时间。

【讨论】:

  • @GonzaloLorieto 在这种情况下,使用[JsonProperty] 来表示没有蛇形的属性。
  • 看起来是一个理想的解决方案,但我可以将它应用于某些类吗?我的输出还可以驼峰式吗?
  • @Rocklan (1) 是的; (2) 不。我已经更新了我的答案以进一步解释。
  • 添加 [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] 正是我所需要的。一个小提示,它是在 Newtonsoft.json 的 9.0.1 版本中添加的。不敢相信答案就这么简单! :) 太感谢了。一旦你知道怎么做总是很容易......但找出方法......这很难。
【解决方案2】:

好吧,您应该可以使用自定义JsonConverter 来读取您的数据。使用Manojs' answer 中提供的反序列化,您可以创建一个DefaultContractResolver,当类具有上面指定的SnakeCasedAttribute 时,它会创建自定义反序列化。

ContractResolver 如下所示

public class SnakeCaseContractResolver : DefaultContractResolver {
  public new static readonly SnakeCaseContractResolver Instance = new SnakeCaseContractResolver();

  protected override JsonContract CreateContract(Type objectType) {
    JsonContract contract = base.CreateContract(objectType);

    if (objectType?.GetCustomAttributes(true).OfType<SnakeCasedAttribute>().Any() == true) {
      contract.Converter = new SnakeCaseConverter();
    }

    return contract;
  }
}

SnakeCaseConverter 会是这样的吗?

public class SnakeCaseConverter : JsonConverter {
  public override bool CanConvert(Type objectType) => objectType.GetCustomAttributes(true).OfType<SnakeCasedAttribute>().Any() == true;
  private static string ConvertFromSnakeCase(string snakeCased) {
    return string.Join("", snakeCased.Split('_').Select(part => part.Substring(0, 1).ToUpper() + part.Substring(1)));
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
    var target = Activator.CreateInstance( objectType );
    var jobject = JObject.Load(reader);

    foreach (var property in jobject.Properties()) {
      var propName = ConvertFromSnakeCase(property.Name);
      var prop = objectType.GetProperty(propName);
      if (prop == null || !prop.CanWrite) {
        continue;
      }
      prop.SetValue(target, property.Value.ToObject(prop.PropertyType, serializer));
    }
    return target;
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
    throw new NotImplementedException();
  }
}

然后你可以使用这个属性(它只是一个占位符)注释你的 dto 类

[SnakeCased]
public class InputObjectDTO {
  public string FullName { get; set; }
  public int TotalPrice { get; set; }
}

作为参考,这是使用的属性

[AttributeUsage(AttributeTargets.Class)]
public class SnakeCasedAttribute : Attribute {
  public SnakeCasedAttribute() {
    // intended blank
  }
}

还有一点需要注意的是,在您当前的形式中,JSON 转换器会抛出错误(“20.00”不是 int),但我猜您可以从这里自己处理该部分 :)

如需完整参考,您可以在 this dotnetfiddle 中查看工作版本

【讨论】:

  • 看起来是一个很好的答案,非常感谢,我明天可以试试。很确定我可以处理小数-> int 错字,但我会看看我是怎么走的:)
【解决方案3】:

您可以添加 cusrom json 转换器代码,如下所示。这应该允许您指定属性映射。

public class ApiErrorConverter : JsonConverter
{
private readonly Dictionary<string, string>     _propertyMappings = new Dictionary<string, string>
{
    {"name", "error"},
    {"code", "errorCode"},
    {"description", "message"}
};

public override bool CanWrite => false;

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    throw new NotImplementedException();
}

public override bool CanConvert(Type objectType)
{
    return objectType.GetTypeInfo().IsClass;
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    object instance = Activator.CreateInstance(objectType);
    var props = objectType.GetTypeInfo().DeclaredProperties.ToList();

    JObject jo = JObject.Load(reader);
    foreach (JProperty jp in jo.Properties())
    {
        if (!_propertyMappings.TryGetValue(jp.Name, out var name))
            name = jp.Name;

        PropertyInfo prop = props.FirstOrDefault(pi =>
            pi.CanWrite && pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);

        prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
    }

    return instance;
    }
}

然后在你的类上指定这个属性。

这应该可行。

本博客解释了使用控制台应用程序的方法。 https://www.jerriepelser.com/blog/deserialize-different-json-object-same-class/

【讨论】:

    猜你喜欢
    • 2022-10-21
    • 1970-01-01
    • 2022-06-11
    • 1970-01-01
    • 1970-01-01
    • 2015-10-11
    • 2017-08-17
    • 2020-02-10
    相关资源
    最近更新 更多