c# json json.net json-deserialization

c# - json deserializar de nombres de propiedad heredados



json.net json-deserialization (3)

Esta pregunta ya tiene una respuesta aquí:

¿Cómo puedo configurar Newtonsoft.Json para deserializar un objeto usando nombres de miembros heredados pero serializarlo usando el nombre de miembro actual?

Aquí hay un objeto de ejemplo que necesita ser serializado y deserializado. Le he dado a una propiedad un atributo que contiene una lista de nombres con los que puede haber sido serializado en el pasado.

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

Me gustaría json serializar siempre usando el nombre "a", pero ser capaz de deserializar la propiedad de cualquier nombre heredado, incluidos "alpha" y "omega", así como el nombre actual, "a"


Esto se puede hacer con un IContractResolver personalizado, creado al extender uno de los resolvers preexistentes, por ejemplo 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; } }

Tenga en cuenta que esta implementación no requiere que la clase tenga una anotación explícita de atributo de contrato de datos. Puede agregar esa restricción, si lo prefiere.

Luego, JsonSerializerSettings con los siguientes JsonSerializerSettings :

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

Por ejemplo:

[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)); } }


Tomé su código y lo modifiqué hacia mi propio estilo, así:

[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; } }


Una solución muy simple que utiliza Json.NET es proporcionar una propiedad heredada solo con un setter.

class TestObject { public int A { get; set; } public int alpha { set => A = value; } public int omega { set => A = value; } }

Probablemente prefiera no tener estos públicos, en cuyo caso puede marcarlos como private y agregar el atributo JsonProperty .

class TestObject { public int A { get; set; } [JsonProperty] private int alpha { set => A = value; } [JsonProperty] private int omega { set => A = value; } }