json десериализовать из устаревших имен свойств

Как настроить Newtonsoft.Json для десериализации объекта с использованием устаревших имен членов, но сериализовать его с использованием текущего имени члена?

Изменить: требуется, чтобы устаревший член был удален из сериализуемого/десериализуемого класса.

Вот пример объекта, который необходимо сериализовать и десериализовать. Я дал свойству атрибут, содержащий список имен, под которыми оно могло быть сериализовано в прошлом.

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

Я хотел бы сериализовать json, всегда используя имя «a», но иметь возможность десериализовать одно свойство из любого устаревшего имени, включая «альфа» и «омега», а также текущее имя «a»


person bboyle1234    schedule 15.10.2015    source источник
comment
Этот вопрос не должен быть закрыт. Аналогичный вопрос, предложенный @Brian Rogers, имеет решение, которое включает в себя сохранение устаревшего члена в классе. Этот вопрос касается удаления устаревшего члена.   -  person bboyle1234    schedule 29.12.2019


Ответы (3)


Это можно сделать с помощью пользовательского IContractResolver, созданного путем расширения 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
{
    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;
    }
}

Затем добавьте атрибуты к вашему типу, как показано в вопросе:

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

Создайте и настройте экземпляр LegacyPropertyResolver, например. следующее:

static IContractResolver legacyResolver = new LegacyPropertyResolver 
{ 
    // Configure as required, e.g. 
    // NamingStrategy = new CamelCaseNamingStrategy() 
};

А затем используйте его в settings:

var settings = new JsonSerializerSettings { ContractResolver = legacyResolver };
var deserialized = JsonConvert.DeserializeObject<TestObject>(jsonString, settings);

Примечания:

Демонстрационная скрипта здесь.

person dbc    schedule 15.10.2015
comment
Боже мой, вау, это невероятно подробный ответ. Спасибо. - person bboyle1234; 15.10.2015

Очень простое решение с использованием Json.NET — просто предоставить устаревшее свойство только с помощью установщика.

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

Вы, вероятно, предпочли бы, чтобы они не были общедоступными, и в этом случае вы можете просто отметить private и добавить атрибут JsonProperty.

class TestObject {
    public int A { get; set; }
    [JsonProperty] private int alpha { set => A = value; }
    [JsonProperty] private int omega { set => A = value; }
}
person Tim Rogers    schedule 18.10.2018
comment
Хороший ответ Тим - person bboyle1234; 18.10.2018

Я взял ваш код и изменил его в соответствии со своим стилем, например:

    [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;
        }
    }
person bboyle1234    schedule 15.10.2015