Проблема сериализации базового класса — не сериализовать свойства производного класса

Я устал сериализовать довольно большой список сущностей, которые все являются производными от базового класса. Мне нужны только свойства базового класса в клиенте. Как мне добиться этого без создания нового экземпляра базового класса?

Я попытался создать собственный ContractResolver, но кажется, что он выполняет getType() во время выполнения вместо использования сериализуемого типа списка/массива.

См. пример кода ниже.

Я хочу добиться. castBaseString == фактическаяBaseString ;

Поэтому я хочу, чтобы castBaseString = [{"Id":1},{"Id":2}] не [{"Value":"value","Id":1},{"Value":"value2","Id":2}]

using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using Newtonsoft.Json;


namespace Tests {

    [TestClass]
    public class JsonNetTest {
        class Base {
            public int Id { get; set; }
        }

        class Derived : Base {
            public string Value { get; set; }
        }
        
        class OtherDerived : Base {
            public string Value { get; set; }
            public string OtherValue { get; set; }
        }

        [TestMethod]
        public void Test() {
            IEnumerable<Derived> deriveds = new Derived[] {
                new Derived {Id = 1, Value = "value" },
                new Derived {Id = 2, Value = "value2" }
            };

            IEnumerable<Base> castBases = deriveds.Cast<Base>().ToList();

            IEnumerable<Base> bases = new Base[] {
                new Base {Id = 1 },
                new Base {Id = 2 }
            };
            JsonSerializerSettings s = new JsonSerializerSettings();

            var derString = JsonConvert.SerializeObject(deriveds, s);
            var castBaseString = JsonConvert.SerializeObject(castBases, s);
            var actualBaseString = JsonConvert.SerializeObject(bases, s);

            Assert.AreEqual(actualBaseString, castBaseString);
            Assert.AreNotEqual(castBaseString, derString);
        }
    }
}

РЕДАКТИРОВАНИЕ НА ОСНОВЕ КОММЕНТАРИЙ Дополнительный контекст:
Я только что опубликовал этот простой тестовый пример для ясности. фактический контекст, в котором это используется, находится в основном приложении aspnet.

Предположим, что есть 3 контроллера.

  1. /api/Производный контроллер/
  2. /api/Другой производный контроллер/
  3. /апи/Базовый Контроллер/

когда клиент вызывает 1, мы хотим вернуть список Derived, когда клиент вызывает 2, мы хотим вернуть список OtherDerived, когда он вызывает 3, мы хотим вернуть список базы

Данные хранятся в 2 разных таблицах в базе данных TBL_DERIVED и TBL_OTHERDERIVED.

Чего мы хотим добиться, когда они вызывают base, так это возврата данных из одной или обеих этих таблиц, но только общих свойств этих таблиц.

Надеюсь, это прояснит.


person DerpDerp    schedule 12.02.2021    source источник


Ответы (3)


Если вы не хотите использовать атрибуты, вы можете использовать ContractResolver для принудительной сериализации только свойств из Base:

public class DerivedTypeFilterContractResolver<T> : DefaultContractResolver {
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        if (property.DeclaringType != typeof(T)) {
            property.ShouldSerialize = instance => false;
        }
        
        return property;
    }
}

Затем используйте его следующим образом:

void Main() {

    IEnumerable<Derived> deriveds = new Derived[] {
                new Derived {Id = 1, Value = "value" },
                new Derived {Id = 2, Value = "value2" }
            };

    IEnumerable<Base> castBases = deriveds.Cast<Base>().ToList();

    IEnumerable<Base> bases = new Base[] {
                new Base {Id = 1 },
                new Base {Id = 2 }
            };

    JsonSerializerSettings s = new JsonSerializerSettings {
        ContractResolver = new DerivedTypeFilterContractResolver<Base>()
    };

    var derString = JsonConvert.SerializeObject(deriveds, s);
    var castBaseString = JsonConvert.SerializeObject(castBases, s);
    var actualBaseString = JsonConvert.SerializeObject(bases, s);

    Console.WriteLine(castBaseString);
}


class Base {
    public int Id { get; set; }
}

class Derived : Base {
    public string Value { get; set; }
}

Выход:

[{"Id":1},{"Id":2}]
person stuartd    schedule 12.02.2021
comment
Это все хорошо, но теперь второе утверждение терпит неудачу, потому что при сериализации Derived сериализует их в '[{Id:1},{Id:2}]' , опуская поле Derived.Value. - person DerpDerp; 12.02.2021
comment
Кроме того, если вы теперь сериализуете любой другой объект, который не является производным от Base, все они будут '[{},{}]' - person DerpDerp; 12.02.2021
comment
Ну да. Вы бы использовали этот распознаватель только тогда, когда вам нужны только значения базового класса. - person stuartd; 12.02.2021
comment
Любые мысли о том, как это сделать в основном проекте aspnet. Я почти уверен, что преобразователь контракта кэшируется при запуске, и вы не можете изменить его во время выполнения из соображений производительности. Я посмотрю, будет ли работать что-то вроде этого: dotnetcoretutorials.com/2018/05/05/ - person DerpDerp; 12.02.2021
comment
Есть какие-нибудь мысли о том, как это сделать в основном проекте aspnet? - вам нужно будет дать более подробную информацию об этом. Вы спросили Я пытаюсь сериализовать довольно большой список сущностей, которые все являются производными от базового класса. Мне нужны только свойства базового класса в клиенте. и использование этого распознавателя контрактов делает именно это. Но теперь вы говорите, что хотите делать это только «иногда»? В какое именно время? И наверняка у вас есть контроль над процессом сериализации, и вы можете делать то, что хотите? - person stuartd; 13.02.2021
comment
Спасибо. см. редактирование выше для дополнительного контекста. Я пытался упростить проблему - person DerpDerp; 14.02.2021

Добавьте [JsonIgnore] верхнюю часть свойства.

class Derived : Base {
    [JsonIgnore]
    public string Value { get; set; }
}
person Berkay    schedule 12.02.2021
comment
Другой вариант — присвоить Derived классу [JsonObject(MemberSerialization = MemberSerialization.OptIn)], чтобы исключить все свойства по умолчанию (нет необходимости отмечать их все JsonIgnoreAttribute). - person Sinatr; 12.02.2021
comment
Для других конкретных вызовов я хочу сериализовать значение, но не при сериализации базового класса. - person DerpDerp; 12.02.2021
comment
Кроме того, у меня нет возможности добавлять атрибуты к типам, с которыми я работаю, поскольку это сторонняя модель данных. - person DerpDerp; 12.02.2021

Оказывается, это невозможно, как работает сериализация json. Ответ @stuartd может быть хорошим обходным путем, если у вас есть только ограниченные случаи, для которых вы хотите это сделать.

person DerpDerp    schedule 04.03.2021