反序列化时的备用属性名称
Posted
技术标签:
【中文标题】反序列化时的备用属性名称【英文标题】:Alternate property name while deserializing 【发布时间】:2013-11-16 12:30:05 【问题描述】:关于这个问题:
How can I change property names when serializing with Json.net?
当然,很好,但是我可以吃蛋糕吗?
我正在寻找一种令人赏心悦目的方式,为属性提供一个备用名称,使字符串可以包含其中任何一个。
类似:
[BetterJsonProperty(PropertyName = "foo_bar")]
public string FooBar get; set;
两个
"FooBar": "yup"
和
"foo_bar":"uhuh"
会按预期反序列化。
作为没有属性的解决方案将起作用或类上的属性,例如:
[AllowCStylePropertyNameAlternatives]
【问题讨论】:
很明显,您希望在反序列化时两者都能工作(并且可以使用自定义的 JsonConverter 或 ContractResolver),但是在序列化时应该使用哪一个?在这种情况下,您不希望两者都被写出来,对吗? 确实!我会选择 FooBar 但这是一个细节。那么哪一个,JsonConverter 或 ContractResolver 以及如何?没有一个属性完全可以正常工作,或者只有一个在类上。 抱歉回复延迟。我添加了一个答案,展示了如何使用JsonConverter
或修改 Json.Net 源代码来完成此操作。希望这会有所帮助。
【参考方案1】:
实现此目的的一种方法是创建自定义JsonConverter
。这个想法是让转换器枚举我们感兴趣的对象的 JSON 属性名称,从名称中去除非字母数字字符,然后尝试通过反射将它们与实际的对象属性匹配。下面是它在代码中的样子:
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]
属性装饰该类,如下所示:
[JsonConverter(typeof(LaxPropertyNameMatchingConverter))]
public class MyClass
public string MyProperty get; set;
public string MyOtherProperty get; set;
下面是转换器的简单演示:
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()
的方法,如下所示:
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 和类属性名称之间进行了多对一映射。
如果您修改此方法以在匹配之前从属性名称中去除所有非字母数字字符,那么您可以获得所需的行为,而无需任何特殊的转换器或属性。这是修改后的代码:
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;
当然,修改源代码也有问题,但我觉得值得一提。
【讨论】:
喜欢这个答案。对更改基本代码有一些内在的阻力,因为我可以看到有人如何做到这一点,对团队什么也没说,并且在某个地方,当有人期望一种行为并得到不同的行为时,混乱就开始了:)【参考方案2】:实现此目的的另一种方法是尽早拦截序列化/反序列化过程,方法是覆盖JsonReader
和JsonWriter
public class CustomJsonWriter : JsonTextWriter
private readonly Dictionary<string, string> _backwardMappings;
public CustomJsonWriter(TextWriter writer, Dictionary<string, string> backwardMappings)
: base(writer)
_backwardMappings = backwardMappings;
public override void WritePropertyName(string name)
base.WritePropertyName(_backwardMappings[name]);
public class CustomJsonReader : JsonTextReader
private readonly Dictionary<string, string> _forwardMappings;
public CustomJsonReader(TextReader reader, Dictionary<string, string> forwardMappings )
: base(reader)
_forwardMappings = forwardMappings;
public override object Value
get
if (TokenType != JsonToken.PropertyName)
return base.Value;
return _forwardMappings[base.Value.ToString()];
做完之后就可以通过做序列化
var mappings = new Dictionary<string, string>
"Property1", "Equivalent1",
"Property2", "Equivalent2",
;
var builder = new StringBuilder();
JsonSerializer.Create().Serialize(new CustomJsonWriter(new StringWriter(builder), mappings), your_object);
并通过执行反序列化
var mappings = new Dictionary<string, string>
"Equivalent1", "Property1",
"Equivalent2", "Property2",
;
var txtReader = new CustomJsonReader(new StringReader(jsonString), mappings);
var your_object = JsonSerializer.Create().Deserialize<Your_Type>(txtReader);
【讨论】:
我一定遗漏了什么,但我想不通。我在这里创建了一个 .Net Fiddle:dotnetfiddle.net/POERAH。每当我调试代码时,不会命中 WritePropertyName 覆盖。正在命中 CustomJsonReader 的 Value 方法,但在字典错误中不存在抛出给定键。 我无法让这个解决方案为我工作,但下面是我最终选择的另一种选择,以防它对其他人有帮助。 ***.com/questions/15915503/…以上是关于反序列化时的备用属性名称的主要内容,如果未能解决你的问题,请参考以下文章
WCF 反序列化中的 XmlException:“名称不能以 '<' 开头”-在自动属性支持字段中