Json deserialize from deprecated property names

How to configure Newtonsoft.Json to deserialize an object using deprecated member names, but serialize it using the current member name?

Here is an example of an object that needs to be serialized and deserialized. I gave a property to an attribute containing a list of names that may have been serialized in the past.

[DataContract] class TestObject { [LegacyDataMemberNames("alpha", "omega")] [DataMember(Name = "a")] public int A { get; set; } } 

I would like json to always serialize using the name "a", but you can deserialize one property from any legacy, including "alpha" and "omega", as well as the current name "a"

+5
source share
2 answers

This can be done using IContractResolver , created by extending one of the pre-existing transformers, for example DefaultContractResolver :

 [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false, Inherited = true)] public class LegacyDataMemberNamesAttribute : Attribute { public LegacyDataMemberNamesAttribute() : this(new string[0]) { } public LegacyDataMemberNamesAttribute(params string[] names) { this.Names = names; } public string [] Names { get; set; } } public class LegacyPropertyResolver : DefaultContractResolver { // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons. // http://www.newtonsoft.com/json/help/html/ContractResolver.htm // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance." static LegacyPropertyResolver instance; static LegacyPropertyResolver() { instance = new LegacyPropertyResolver(); } public static LegacyPropertyResolver Instance { get { return instance; } } protected LegacyPropertyResolver() : base() { } protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { var properties = base.CreateProperties(type, memberSerialization); for (int i = 0, n = properties.Count; i < n; i++) { var property = properties[i]; if (!property.Writable) continue; var attrs = property.AttributeProvider.GetAttributes(typeof(LegacyDataMemberNamesAttribute), true); if (attrs == null || attrs.Count == 0) continue; // Little kludgy here: use MemberwiseClone to clone the JsonProperty. var clone = property.GetType().GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); foreach (var name in attrs.Cast<LegacyDataMemberNamesAttribute>().SelectMany(a => a.Names)) { if (properties.Any(p => p.PropertyName == name)) { Debug.WriteLine("Duplicate LegacyDataMemberNamesAttribute: " + name); continue; } var newProperty = (JsonProperty)clone.Invoke(property, new object[0]); newProperty.Readable = false; newProperty.PropertyName = name; properties.Add(newProperty); } } return properties; } } 

Note that this implementation does not require the class to have explicit data contract attribute annotations. You can add this restriction if you want.

Then use it with the following JsonSerializerSettings :

 var settings = new JsonSerializerSettings { ContractResolver = LegacyPropertyResolver.Instance }; 

For instance:

 [DataContract] class TestObject { [LegacyDataMemberNames("alpha", "omega")] [DataMember(Name = "a")] public int A { get; set; } } public static class JsonExtensions { public static void RenameProperty(this JObject obj, string oldName, string newName) { if (obj == null) throw new NullReferenceException(); var property = obj.Property(oldName); if (property != null) { property.Replace(new JProperty(newName, property.Value)); } } } public class TestClass { public static void Test() { try { TestInner(); } catch (Exception ex) { Debug.Assert(false, ex.ToString()); // No assert throw; } } public static void TestInner() { var test = new TestObject { A = 42 }; var settings = new JsonSerializerSettings { ContractResolver = LegacyPropertyResolver.Instance }; var json = JObject.FromObject(test, JsonSerializer.CreateDefault(settings)); if (json.SelectToken("alpha") != null || json.SelectToken("omega") != null) throw new InvalidOperationException("Failed serialization"); Test(test, json); json.RenameProperty("a", "alpha"); Test(test, json); json.RenameProperty("alpha", "omega"); Test(test, json); } private static void Test(TestObject test, JObject json) { var test1 = json.ToObject<TestObject>(JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = LegacyPropertyResolver.Instance })); if (test1.A != test.A) throw new InvalidOperationException("Failed deserialization"); Console.WriteLine("Successfully deserialized: " + json.ToString(Formatting.None)); Debug.WriteLine("Successfully deserialized: " + json.ToString(Formatting.None)); } } 
+4
source

I took your code and changed it in my style, for example:

  [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false, Inherited = true)] public class LegacyDataMemberNamesAttribute : Attribute { public readonly string[] LegacyNames; public LegacyDataMemberNamesAttribute(params string[] legacyNames) { LegacyNames = legacyNames; } } public class LegacyPropertyResolver : DefaultContractResolver { // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons. // http://www.newtonsoft.com/json/help/html/ContractResolver.htm // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance." public static readonly LegacyPropertyResolver Instance = new LegacyPropertyResolver(); protected LegacyPropertyResolver() : base() { } protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { var properties = base.CreateProperties(type, memberSerialization); foreach (var property in properties.ToArray()) { if (!property.Writable) continue; foreach (var legacyName in GetLegacyNames(property)) { properties.Add(CloneWithLegacyName(property, legacyName)); } } return properties; } static IEnumerable<string> GetLegacyNames(JsonProperty property) { return property.AttributeProvider.GetAttributes(typeof(LegacyDataMemberNamesAttribute), true) .Cast<LegacyDataMemberNamesAttribute>() .SelectMany(a => a.LegacyNames) .Distinct(); } static readonly object[] _emptyObjectArray = new object[0]; static readonly MethodInfo _propertyClone = typeof(JsonProperty).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); static JsonProperty CloneWithLegacyName(JsonProperty property, string legacyName) { var legacyProperty = (JsonProperty)_propertyClone.Invoke(property, _emptyObjectArray); legacyProperty.Readable = false; legacyProperty.PropertyName = legacyName; return legacyProperty; } } 
+1
source

Source: https://habr.com/ru/post/1233806/


All Articles