我正在尝试从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转换器?
答案 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包含三种命名策略:SnakeCaseNamingStrategy
,CamelCaseNamingStrategy
和DefaultNamingStrategy
。)
现在,如果您要对同一个类别使用一种命名策略进行反序列化,而对另一类使用不同的策略进行 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/