我正在尝试从WebApi v2(完整框架,而不是dot net core)中的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 转换器?
无需重新发明轮子。 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 包含三种命名策略:SnakeCaseNamingStrategy
、CamelCaseNamingStrategy
和DefaultNamingStrategy
。
并对同一类使用不同的策略进行序列化,则上述解决方案都不适合您,因为命名策略将在 Web API 中双向应用。 因此,在这种情况下,您将需要一些自定义内容,例如@icepickle答案中显示的内容,以控制何时应用每个答案。
好吧,您应该能够使用自定义JsonConverter
来读取数据。使用 Manojs 答案中提供的反序列化,您可以创建一个DefaultContractResolver
,当类具有上面指定的SnakeCasedAttribute
时,该将创建自定义反序列化。
契约解析器如下所示
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"不是整数),但我猜从这里您可以自己处理该部分:)
有关完整的参考,您可以在此dotnetfiddle中看到工作版本
您可以添加 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/