问题描述
关于这个问题:
使用 Json.net 序列化时如何更改属性名称?
当然,很好,但是我可以吃蛋糕吗?
Sure, great, but can I have the cake and eat it?
我正在寻找一种令人赏心悦目的方式,为属性提供备用名称,使字符串可以包含其中任何一个.
What I'm looking for is an eye pleasing way have an alternate name for a property in such a way that the string may contain either.
类似:
[BetterJsonProperty(PropertyName = "foo_bar")]
public string FooBar { get; set; }
两个
{
"FooBar": "yup"
}
和
{
"foo_bar":"uhuh"
}
会按预期反序列化.
作为没有属性的解决方案将起作用或类上的属性,例如:
As solution with no attribute would work or an attribute on the class like:
[AllowCStylePropertyNameAlternatives]
推荐答案
实现此目的的一种方法是创建一个自定义 JsonConverter
.这个想法是让转换器枚举我们感兴趣的对象的 JSON 属性名称,从名称中去除非字母数字字符,然后尝试通过反射将它们与实际的对象属性匹配.下面是它在代码中的样子:
One way to accomplish this is to create a custom JsonConverter
. The idea is to have the converter enumerate the JSON property names for objects we are interested in, strip the non-alphanumeric characters from the names and then try to match them up with the actual object properties via reflection. Here is how it might look in code:
public class LaxPropertyNameMatchingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsClass;
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
PropertyInfo[] props = objectType.GetProperties();
JObject jo = JObject.Load(reader);
foreach (JProperty jp in jo.Properties())
{
string name = Regex.Replace(jp.Name, "[^A-Za-z0-9]+", "");
PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && string.Equals(pi.Name, name, StringComparison.OrdinalIgnoreCase));
if (prop != null)
prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
要将自定义转换器用于特定类,您可以使用 [JsonConverter]
属性装饰该类,如下所示:
To use the custom converter with a particular class, you can decorate that class with a [JsonConverter]
attribute like this:
[JsonConverter(typeof(LaxPropertyNameMatchingConverter))]
public class MyClass
{
public string MyProperty { get; set; }
public string MyOtherProperty { get; set; }
}
这里是转换器的简单演示:
Here is a simple demo of the converter in action:
class Program
{
static void Main(string[] args)
{
string json = @"
[
{
""my property"" : ""foo"",
""my-other-property"" : ""bar"",
},
{
""(myProperty)"" : ""baz"",
""myOtherProperty"" : ""quux""
},
{
""MyProperty"" : ""fizz"",
""MY_OTHER_PROPERTY"" : ""bang""
}
]";
List<MyClass> list = JsonConvert.DeserializeObject<List<MyClass>>(json);
foreach (MyClass mc in list)
{
Console.WriteLine(mc.MyProperty);
Console.WriteLine(mc.MyOtherProperty);
}
}
}
输出:
foo
bar
baz
quux
fizz
bang
虽然此解决方案在大多数情况下都可以完成这项工作,但如果您同意直接更改 Json.Net 源代码,还有一个更简单的解决方案 .事实证明,您只需向 Newtonsoft.Json.Serialization.JsonPropertyCollection
类添加一行代码即可完成相同的操作.在此类中,有一个名为 GetClosestMatchProperty()
的方法,如下所示:
While this solution should do the job in most cases, there is an even simpler solution if you are OK with the idea of changing the Json.Net source code directly. It turns out you can accomplish the same thing by adding just one line of code to the Newtonsoft.Json.Serialization.JsonPropertyCollection
class. In this class, there is a method called GetClosestMatchProperty()
which looks like this:
public JsonProperty GetClosestMatchProperty(string propertyName)
{
JsonProperty property = GetProperty(propertyName, StringComparison.Ordinal);
if (property == null)
property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);
return property;
}
在反序列化程序调用此方法时,JsonPropertyCollection
包含正在反序列化的类的所有属性,propertyName
参数包含正在匹配的 JSON 属性名称.如您所见,该方法首先尝试完全匹配名称,然后尝试不区分大小写的匹配.因此,我们已经在 JSON 和类属性名称之间进行了多对一映射.
At the point where this method is called by the deserializer, the JsonPropertyCollection
contains all the properties from the class being deserialized, and the propertyName
parameter contains the name of the JSON property name being matched. As you can see, the method first tries an exact name match, then it tries a case-insensitive match. So we already have a many-to-one mapping being done between the JSON and class property names.
如果您修改此方法以在匹配之前从属性名称中去除所有非字母数字字符,那么您可以获得所需的行为,而无需任何特殊的转换器或属性.这是修改后的代码:
If you modify this method to strip out all non-alphanumeric characters from the property name prior to matching it, then you can get the behavior you desire, without any special converters or attributes needed. Here is the modified code:
public JsonProperty GetClosestMatchProperty(string propertyName)
{
propertyName = Regex.Replace(propertyName, "[^A-Za-z0-9]+", "");
JsonProperty property = GetProperty(propertyName, StringComparison.Ordinal);
if (property == null)
property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);
return property;
}
当然,修改源代码也有问题,但我觉得值得一提.
Of course, modifying the source code has its problems as well, but I figured it was worth a mention.
这篇关于反序列化时的备用属性名称的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!