从WebApi中的蛇案JSON自动绑定Pascal Case C#模型

时间:2019-02-05 06:01:04

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

我正在尝试从WebApi v2(完整框架,而不是点网核心)中的snake_cased JSON绑定我的PascalCased c#模型。

这是我的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转换器?

3 个答案:

答案 0 :(得分: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”不是整数),但我想您可以从这里自己处理该部分了:)

有关完整的参考,您可以在this dotnetfiddle

中看到工作版本。

答案 1 :(得分:2)

无需重新发明轮子。 Json.Net已经有了一个SnakeCaseNamingStrategy类,可以完全执行您想要的操作。您只需通过设置将其设置为NamingStrategy上的DefaultContractResolver

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

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


现在,如果您要对同一个类别使用一种命名策略进行反序列化,而对另一类使用不同的策略进行 serialize ,则以上两种解决方案均不起作用为您服务,因为命名策略将在Web API中双向应用。因此,在这种情况下,您将需要类似@icepickle的answer所示的自定义控件,以控制何时应用每一个。

答案 2 :(得分:1)

您可以添加如下的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/