Как с помощью JSON.net предотвратить сериализацию свойств производного класса при использовании в контексте базового класса?

Учитывая модель данных:

[DataContract]
public class Parent
{
    [DataMember]
    public IEnumerable<ChildId> Children { get; set; }
}

[DataContract]
public class ChildId
{
    [DataMember]
    public string Id { get; set; }
}

[DataContract]
public class ChildDetail : ChildId
{
    [DataMember]
    public string Name { get; set; }
}

По соображениям удобства реализации бывают случаи, когда ChildId объекты на Parent на самом деле являются ChildDetail объектами. Когда я использую JSON.net для сериализации Parent, они записываются со всеми свойствами ChildDetail.

Есть ли способ указать JSON.net (или любому другому сериализатору JSON, я недостаточно глубоко в проекте, чтобы быть привязанным к одному) игнорировать свойства производного класса при сериализации в качестве базового класса?

РЕДАКТИРОВАТЬ: важно, чтобы при сериализации производного класса напрямую я мог создавать все свойства. Я только хочу запретить полиморфизм в объекте Parent.


person Christopher Currie    schedule 03.05.2011    source источник


Ответы (6)


Я использую пользовательский Contract Resolver, чтобы ограничить, какие из моих свойств сериализовать. Это может указать вам правильное направление.

e.g.

/// <summary>
/// json.net serializes ALL properties of a class by default
/// this class will tell json.net to only serialize properties if they MATCH 
/// the list of valid columns passed through the querystring to criteria object
/// </summary>
public class CriteriaContractResolver<T> : DefaultContractResolver
{
    List<string> _properties;

    public CriteriaContractResolver(List<string> properties)
    {
        _properties = properties
    }

    protected override IList<JsonProperty> CreateProperties(
        JsonObjectContract contract)
    {
        IList<JsonProperty> filtered = new List<JsonProperty>();

        foreach (JsonProperty p in base.CreateProperties(contract))
            if(_properties.Contains(p.PropertyName)) 
                filtered.Add(p);

        return filtered;
    }
}

В функции переопределения IList вы можете использовать отражение, чтобы заполнить список только родительскими свойствами.

Сопоставитель контрактов применяется к вашему сериализатору json.net. Этот пример взят из приложения asp.net mvc.

JsonNetResult result = new JsonNetResult();
result.Formatting = Formatting.Indented;
result.SerializerSettings.ContractResolver = 
    new CriteriaContractResolver<T>(Criteria);
person Jason Watts    schedule 03.05.2011
comment
Это именно то, что я искал. Частичный ответ на основе полей, переданных в URI. Спасибо! [Интересно, почему это не встроенный преобразователь контрактов] - person David Kassa; 28.04.2012
comment
У вас есть пример класса IRestCriteria ‹T›? Откуда это взялось? - person Micah; 12.06.2012
comment
Мика - Наверное, мне не следовало просто вырезать и наклеивать. С тех пор я изменил этот класс, чтобы включить Список ‹string› имен свойств. - person Jason Watts; 12.06.2012
comment
Я не думаю, что вам следует вызывать base.CreateProperties, потому что вы не вызываете новый метод переопределения, который вы написали (принимает только один параметр), поэтому вам пришлось передать 2 параметра вместо простого контракта. - person Gui; 13.08.2012
comment
Это то, что мне нужно, за исключением того, как я могу установить это поведение только для определенных типов классов или включить разные наборы свойств для разных типов? - person Rn222; 19.09.2012
comment
Это было полезно !! Я бы посоветовал использовать HashSet вместо List, так как это сделает if (_properties.Contains(p.PropertyName)) быстрее. - person Lea Hayes; 08.05.2014
comment
Кое-что интересно - это не работает при наследовании от CamelCasePropertyNamesContractResolver. Этот класс кэширует результаты создания свойства, поэтому вы всегда будете получать одни и те же результаты. Однако наследование от DefaultContractResolver (как указано выше) работает нормально. - person Rich; 07.01.2016
comment
@Rich Если вам нужна оболочка для верблюда, вы можете просто изменить JsonProperty.PropertyName на свою версию корпуса для верблюда. Очень легко реализовать самому - person fjch1997; 13.06.2017

У меня была точно такая же проблема, и я посмотрел, как создать ContractResolver, который я действительно искал, и который лучше ответил на этот вопрос. Это только сериализует свойства типа T, который вы действительно хотите сериализовать, но в этом примере вы также можете легко создать аналогичные подходы:

public class TypeOnlyContractResolver<T> : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        property.ShouldSerialize = instance => property.DeclaringType == typeof (T);
        return property;
    }
}
person Akku    schedule 03.05.2013
comment
Именно то, что я искал - у меня есть базовый класс, содержащий некоторые свойства, которые я не хочу раскрывать. - person Edi; 25.05.2016
comment
Рассмотрите возможность перехода на property.ShouldSerialize = instance => property.DeclaringType == typeof(T) || typeof(T).IsSubclassOf(property.DeclaringType);, если класс, который вы пытаетесь сериализовать, является подклассом другого класса. - person fjch1997; 13.06.2017

Столкнувшись с подобной проблемой, я придумал ContractResolver:

public class StrictTypeContractResolver : DefaultContractResolver
{
    private readonly Type _targetType;

    public StrictTypeContractResolver( Type targetType ) => _targetType = targetType;

    protected override IList<JsonProperty> CreateProperties( Type type, MemberSerialization memberSerialization )
        => base.CreateProperties
        (
            _targetType.IsAssignableFrom( type ) ? _targetType : type,
            memberSerialization
        );
}

Он отсекает только свойства потомков targetType, не затрагивая свойства его базовых классов или других типов, на которые могут ссылаться свойства targetType. Что, в зависимости от ваших потребностей, может или не может быть улучшением по сравнению с другими ответами, предоставленными здесь в то время.

person HellBrick    schedule 17.05.2017
comment
На мой взгляд, лучшее решение, так как оно также работает со ссылочными объектами / вложенными структурами. - person martinoss; 24.11.2017

Ознакомьтесь с ответами в этой же теме, особенно IgnorableSerializerContractResolver в моем ответе и более приятный лямбда-версия

Использование:

var jsonResolver = new IgnorableSerializerContractResolver();
// ignore single property
jsonResolver.Ignore(typeof(Company), "WebSites");
// ignore single datatype
jsonResolver.Ignore(typeof(System.Data.Objects.DataClasses.EntityObject));
var jsonSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, ContractResolver = jsonResolver };
person drzaus    schedule 05.06.2013

Я не использовал JSON.Net в частности, поэтому не уверен, что это вам поможет. Если JSON.Net является производным от системы сериализации .Net, тогда вы сможете добавить атрибут [NonSerialized] к своим свойствам, которые теперь хотите сериализовать в базовом классе. Когда вы вызываете методы сериализации в базовом классе, сериализация должна пропустить эти элементы.

person Bueller    schedule 03.05.2011
comment
Немного отличается от того, что я хочу, а именно то, что создаются все свойства базового класса и ни одного свойств производного класса. Я не хочу добавлять [NonSeralized] к свойствам производного класса, потому что временами я хочу, чтобы производный класс сериализовался как есть, но не тогда, когда он используется в контексте базового класса. - person Christopher Currie; 03.05.2011
comment
Эти ссылки могут помочь. Похоже, вам нужно будет выполнить настраиваемую сериализацию. Статья MSDN, Статья CodeProject - person Bueller; 03.05.2011
comment
@Bueller Это не поможет, если класс, от которого вы наследуете, является собственным классом, который вы не можете изменить. - person Brad Moore; 27.07.2014

Не сравнивал влияние на производительность, но это тоже рабочее решение, которое также работает с вложенными / ссылочными объектами.

Derived d = new Derived();           
string jsonStringD = JsonConvert.SerializeObject(d);
Base b = new Base();
JsonConvert.PopulateObject(jsonStringD, b);
string jsonStringB = JsonConvert.SerializeObject(b);
person martinoss    schedule 24.11.2017