Приведение интерфейсов для десериализации в JSON.NET

129

Я пытаюсь настроить считыватель, который будет принимать объекты JSON с различных веб-сайтов (подумайте о сборке информации) и переводит их в объекты C #. В настоящее время я использую JSON.NET для процесса десериализации. Проблема, с которой я сталкиваюсь, заключается в том, что он не знает, как обрабатывать свойства уровня интерфейса в классе. Итак, что-то от природы:

public IThingy Thing

Произойдет ошибка:

Не удалось создать экземпляр типа IThingy. Тип - это интерфейс или абстрактный класс, и его нельзя создать.

Относительно важно, чтобы он был IThingy, а не Thingy, поскольку код, над которым я работаю, считается чувствительным, а модульное тестирование очень важно. Мокинг объектов для скриптов атомарного тестирования невозможен с полноценными объектами, такими как Thingy. Они должны быть интерфейсом.

Я уже некоторое время изучаю документацию JSON.NET, и все вопросы, которые я мог найти на этом сайте, связанные с этим, возникли более года назад. Любая помощь?

Кроме того, если это важно, мое приложение написано на .NET 4.0.

tmesser
источник

Ответы:

115

@SamualDavis предоставил отличное решение в связанном вопросе , который я резюмирую здесь.

Если вам необходимо десериализовать поток JSON в конкретный класс, который имеет свойства интерфейса, вы можете включить конкретные классы в качестве параметров в конструктор класса! Десериализатор NewtonSoft достаточно умен, чтобы понять, что ему нужно использовать эти конкретные классы для десериализации свойств.

Вот пример:

public class Visit : IVisit
{
    /// <summary>
    /// This constructor is required for the JSON deserializer to be able
    /// to identify concrete classes to use when deserializing the interface properties.
    /// </summary>
    public Visit(MyLocation location, Guest guest)
    {
        Location = location;
        Guest = guest;
    }
    public long VisitId { get; set; }
    public ILocation Location { get;  set; }
    public DateTime VisitDate { get; set; }
    public IGuest Guest { get; set; }
}
Марк Меуэр
источник
15
Как это будет работать с ICollection? ICollection <IGuest> Гости {get; set;}
DrSammyD
12
Он работает с ICollection <ConcreteClass>, поэтому ICollection <Guest> работает. Так же, как и к сведению, вы можете поместить атрибут [JsonConstructor] в свой конструктор, чтобы он использовал его по умолчанию, если у вас есть несколько конструкторов
DrSammyD
6
Я застрял в той же проблеме, в моем случае у меня есть несколько реализаций интерфейса (в вашем примере это интерфейс ILocation), так что, если есть классы, такие как MyLocation, VIPLocation, OrdinaryLocation. Как сопоставить их со свойством Location? Если у вас есть только одна реализация, например MyLocation, это просто, но как это сделать, если существует несколько реализаций ILocation?
ATHER
10
Если у вас более одного конструктора, вы можете пометить свой специальный конструктор с помощью [JsonConstructor]атрибута.
Д-р Роб Ланг,
26
Это совсем не нормально. Смысл использования интерфейсов заключается в использовании внедрения зависимостей, но, выполняя это с параметром типа объекта, который требуется вашему конструктору, вы полностью теряете смысл наличия интерфейса в качестве свойства.
Jérôme MEVEL
57

(Скопировано из этого вопроса )

В случаях, когда у меня не было контроля над входящим JSON (и поэтому я не могу гарантировать, что он включает свойство $ type), я написал собственный преобразователь, который просто позволяет вам явно указать конкретный тип:

public class Model
{
    [JsonConverter(typeof(ConcreteTypeConverter<Something>))]
    public ISomething TheThing { get; set; }
}

Здесь просто используется реализация сериализатора по умолчанию из Json.Net с явным указанием конкретного типа.

Обзор доступен в этом сообщении в блоге . Исходный код ниже:

public class ConcreteTypeConverter<TConcrete> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        //assume we can convert to anything for now
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //explicitly specify the concrete type we want to create
        return serializer.Deserialize<TConcrete>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //use the default serialization - it works fine
        serializer.Serialize(writer, value);
    }
}
Стив Грейтрекс
источник
11
Мне очень нравится этот подход, и я применил его в нашем собственном проекте. Я даже добавил ConcreteListTypeConverter<TInterface, TImplementation>для обработки членов класса типа IList<TInterface>.
Оливер
3
Это отличный код. concreteTypeConverterХотя было бы лучше иметь фактический код в вопросе.
Крис
2
@Oliver - Вы можете опубликовать свою ConcreteListTypeConverter<TInterface, TImplementation>реализацию?
Майкл
2
А если у вас есть два разработчика ISomething?
bdaniel7 01
56

Зачем нужен конвертер? Для Newtonsoft.Jsonрешения этой конкретной проблемы есть встроенная функция :

Набор TypeNameHandlingв JsonSerializerSettingsкTypeNameHandling.Auto

JsonConvert.SerializeObject(
  toSerialize,
  new JsonSerializerSettings()
  {
    TypeNameHandling = TypeNameHandling.Auto
  });

Это поместит каждый тип в json, который хранится не как конкретный экземпляр типа, а как интерфейс или абстрактный класс.

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

Я протестировал его, и он отлично работает даже со списками.

Результаты поиска Веб-результат со ссылками на сайты

⚠️ ВНИМАНИЕ :

Используйте это только для json из известного и надежного источника. Пользователь snipsnipsnip правильно заметил, что это действительно уязвимость.

См. CA2328 и SCS0028 для получения дополнительной информации.


Источник и альтернативная ручная реализация: Блог Code Inside

Mafii
источник
3
Отлично, это помогло мне создать быстрый и грязный глубокий клон ( stackoverflow.com/questions/78536/deep-cloning-objects )
Compufreak
1
@Shimmy Objects: «Включите имя типа .NET при сериализации в структуру объекта JSON». Авто: включить имя типа .NET, если тип сериализуемого объекта не совпадает с его объявленным типом. Обратите внимание, что по умолчанию это не включает корневой сериализованный объект. Чтобы включить имя типа корневого объекта в JSON, вы должны указать объект корневого типа с помощью SerializeObject (Object, Type, JsonSerializerSettings) или Serialize (JsonWriter, Object, Type). Источник: newtonsoft.com/json/help/html/…
Mafii
4
Я только что попробовал это при десериализации, и это не сработало. Тема этого вопроса о переполнении стека: «Приведение интерфейсов для десериализации в JSON.NET»
Джастин Руссо
3
@JustinRusso, он работает только тогда, когда json был сериализован с той же настройкой
Mafii
3
Проголосуйте за быстрое, если не грязное, решение. Если вы просто сериализуете конфигурации, это работает. Лучше остановить разработку для создания преобразователей и, конечно, лучше, чем украшение каждой введенной собственности. serializer.TypeNameHandling = TypeNameHandling.Auto; JsonConvert.DefaultSettings (). TypeNameHandling = TypeNameHandling.Auto;
Шон Андерсон
39

Чтобы включить десериализацию нескольких реализаций интерфейсов, вы можете использовать JsonConverter, но не через атрибут:

Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Converters.Add(new DTOJsonConverter());
Interfaces.IEntity entity = serializer.Deserialize(jsonReader);

DTOJsonConverter сопоставляет каждый интерфейс с конкретной реализацией:

class DTOJsonConverter : Newtonsoft.Json.JsonConverter
{
    private static readonly string ISCALAR_FULLNAME = typeof(Interfaces.IScalar).FullName;
    private static readonly string IENTITY_FULLNAME = typeof(Interfaces.IEntity).FullName;


    public override bool CanConvert(Type objectType)
    {
        if (objectType.FullName == ISCALAR_FULLNAME
            || objectType.FullName == IENTITY_FULLNAME)
        {
            return true;
        }
        return false;
    }

    public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        if (objectType.FullName == ISCALAR_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientScalar));
        else if (objectType.FullName == IENTITY_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientEntity));

        throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
    }

    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

DTOJsonConverter требуется только для десериализатора. Процесс сериализации не изменился. В объект Json не нужно встраивать имена конкретных типов.

В этом сообщении SO предлагается то же решение на один шаг вперед с помощью универсального JsonConverter.

Эрик Бумендиль
источник
Не вызовет ли этот метод WriteJson метода serializer.Serialize переполнение стека, поскольку вызов сериализации для сериализуемого преобразователем значения приведет к повторному рекурсивному вызову метода WriteJson преобразователя?
Трийнко 02
Этого не должно быть, если метод CanConvert () возвращает согласованный результат.
Эрик Бумендиль
3
Почему вы сравниваете FullNames, когда вы можете просто сравнивать типы напрямую?
Алекс Жуковский
Просто сравнение типов тоже нормально.
Эрик Бумендиль 01
23

Используйте этот класс для сопоставления абстрактного типа с реальным типом:

public class AbstractConverter<TReal, TAbstract> : JsonConverter where TReal : TAbstract
{
    public override Boolean CanConvert(Type objectType) 
        => objectType == typeof(TAbstract);

    public override Object ReadJson(JsonReader reader, Type type, Object value, JsonSerializer jser) 
        => jser.Deserialize<TReal>(reader);

    public override void WriteJson(JsonWriter writer, Object value, JsonSerializer jser) 
        => jser.Serialize(writer, value);
}

... и при десериализации:

        var settings = new JsonSerializerSettings
        {
            Converters = {
                new AbstractConverter<Thing, IThingy>(),
                new AbstractConverter<Thing2, IThingy2>()
            },
        };

        JsonConvert.DeserializeObject(json, type, settings);
Gildor
источник
1
Мне очень нравится хороший краткий ответ, решающий мою проблему. Не нужен автофак или что-то еще!
Ben Power
3
Стоит указать это в объявлении класса конвертера: where TReal : TAbstractчтобы убедиться, что он может быть приведен к типу
Артемиус
1
Более полное, где могло бы быть where TReal : class, TAbstract, new().
Эрик Филипс,
2
Я тоже использовал этот конвертер со структурой, я считаю, что «где TReal: TAbstract» достаточно. Спасибо всем.
Gildor
2
Золото! Отличный способ пойти.
SwissCoder
12

Николас Уэстби предоставил отличное решение в замечательной статье .

Если вы хотите десериализацию JSON в один из множества возможных классов, реализующих такой интерфейс:

public class Person
{
    public IProfession Profession { get; set; }
}

public interface IProfession
{
    string JobTitle { get; }
}

public class Programming : IProfession
{
    public string JobTitle => "Software Developer";
    public string FavoriteLanguage { get; set; }
}

public class Writing : IProfession
{
    public string JobTitle => "Copywriter";
    public string FavoriteWord { get; set; }
}

public class Samples
{
    public static Person GetProgrammer()
    {
        return new Person()
        {
            Profession = new Programming()
            {
                FavoriteLanguage = "C#"
            }
        };
    }
}

Вы можете использовать собственный конвертер JSON:

public class ProfessionConverter : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(IProfession);
    }
    public override void WriteJson(JsonWriter writer,
        object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var profession = default(IProfession);
        switch (jsonObject["JobTitle"].Value())
        {
            case "Software Developer":
                profession = new Programming();
                break;
            case "Copywriter":
                profession = new Writing();
                break;
        }
        serializer.Populate(jsonObject.CreateReader(), profession);
        return profession;
    }
}

И вам нужно будет украсить свойство "Profession" атрибутом JsonConverter, чтобы дать ему знать, что нужно использовать ваш собственный конвертер:

    public class Person
    {
        [JsonConverter(typeof(ProfessionConverter))]
        public IProfession Profession { get; set; }
    }

А затем вы можете преобразовать свой класс с помощью интерфейса:

Person person = JsonConvert.DeserializeObject<Person>(jsonString);
А. Морель
источник
8

Вы можете попробовать две вещи:

Реализуйте модель пробного / синтаксического анализа:

public class Organisation {
  public string Name { get; set; }

  [JsonConverter(typeof(RichDudeConverter))]
  public IPerson Owner { get; set; }
}

public interface IPerson {
  string Name { get; set; }
}

public class Tycoon : IPerson {
  public string Name { get; set; }
}

public class Magnate : IPerson {
  public string Name { get; set; }
  public string IndustryName { get; set; }
}

public class Heir: IPerson {
  public string Name { get; set; }
  public IPerson Benefactor { get; set; }
}

public class RichDudeConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return (objectType == typeof(IPerson));
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    // pseudo-code
    object richDude = serializer.Deserialize<Heir>(reader);

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Magnate>(reader);
    }

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Tycoon>(reader);
    }

    return richDude;
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

Или, если вы можете сделать это в своей объектной модели, реализуйте конкретный базовый класс между IPerson и конечными объектами и десериализуйте его.

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

MCW
источник
Модель «попытка / анализ» невозможна из-за масштаба, с которым мне приходится работать. Я должен рассмотреть область действия сотен базовых объектов с еще более сотнями объектов-заглушек / вспомогательных объектов для представления встроенных объектов JSON, которые встречаются часто. Не исключено изменение объектной модели, но разве использование конкретного базового класса в свойствах не лишит нас возможности имитировать элементы для модульного тестирования? Или я как-то отстал?
tmesser
Вы все еще можете реализовать макет от IPerson - обратите внимание, что тип свойства Organisation.Owner по-прежнему IPerson. Но для десериализации произвольной цели вам нужно вернуть конкретный тип. Если вы не владеете определением типа и не можете определить минимальный набор свойств, который потребуется вашему коду, тогда ваше последнее средство - это что-то вроде мешка ключ / значение. Используя ваш пример комментария в facebook - можете ли вы опубликовать в ответ, как выглядят ваши (одна или несколько) реализаций ILocation? Это может помочь продвинуться вперед.
mcw
Поскольку основная надежда - насмешка, интерфейс ILocation на самом деле является просто фасадом для конкретного объекта Location. Быстрый пример, который я только что придумал, будет примерно таким ( pastebin.com/mWQtqGnB ) для интерфейса и this ( pastebin.com/TdJ6cqWV ) для конкретного объекта.
tmesser
И чтобы перейти к следующему шагу, это пример того, как будут выглядеть IPage ( pastebin.com/iuGifQXp ) и Page ( pastebin.com/ebqLxzvm ). Проблема, конечно, в том, что, хотя десериализация страницы в целом работает нормально, она задыхается, когда добирается до свойства ILocation.
tmesser
Итак, подумайте об объектах, которые вы на самом деле очищаете и десериализуете - так ли обычно, что данные JSON согласуются с одним определением конкретного класса? Значит (гипотетически) вы не встретите «местоположения» с дополнительными свойствами, которые сделали бы Location непригодным для использования в качестве конкретного типа для десериализованного объекта? Если это так, то приписывание свойства ILocation страницы «LocationConverter» должно работать. Если нет, и это потому, что данные JSON не всегда соответствуют жесткой или согласованной структуре (например, ILocation), тогда (... продолжение)
mcw
8

Я нашел это полезным. Вы тоже могли бы.

Пример использования

public class Parent
{
    [JsonConverter(typeof(InterfaceConverter<IChildModel, ChildModel>))]
    IChildModel Child { get; set; }
}

Конвертер пользовательского творчества

public class InterfaceConverter<TInterface, TConcrete> : CustomCreationConverter<TInterface>
    where TConcrete : TInterface, new()
{
    public override TInterface Create(Type objectType)
    {
        return new TConcrete();
    }
}

Документация Json.NET

smiggleworth
источник
1
Не подходящее решение. Не обращается к спискам и приводит к разбрызгиванию декораторов / аннотаций повсюду.
Шон Андерсон
5

Для тех, кому может быть интересно узнать о ConcreteListTypeConverter, на который ссылался Оливер, вот моя попытка:

public class ConcreteListTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface 
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var res = serializer.Deserialize<List<TImplementation>>(reader);
        return res.ConvertAll(x => (TInterface) x);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}
Мэтт М
источник
1
Меня смущает переопределение CanConvert(Type objectType) { return true;}. Кажется хакерским, насколько это полезно? Возможно, я ошибаюсь, но разве это не то же самое, что сказать маленькому неопытному бойцу, что он выиграет бой независимо от соперника?
Chef_Code
4

Что бы это ни стоило, мне пришлось по большей части справляться с этим самому. У каждого объекта есть метод Deserialize (string jsonStream) . Несколько его фрагментов:

JObject parsedJson = this.ParseJson(jsonStream);
object thingyObjectJson = (object)parsedJson["thing"];
this.Thing = new Thingy(Convert.ToString(thingyObjectJson));

В этом случае new Thingy (string) - это конструктор, который будет вызывать метод Deserialize (string jsonStream) соответствующего конкретного типа. Эта схема будет продолжать идти вниз и вниз, пока вы не дойдете до базовых точек, которые json.NET может просто обработать.

this.Name = (string)parsedJson["name"];
this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);

Так далее и тому подобное. Эта установка позволила мне предоставить настройки json.NET, с которыми он может справиться, без необходимости рефакторинга большой части самой библиотеки или использования громоздких моделей try / parse, которые увязли бы всю нашу библиотеку из-за количества задействованных объектов. Это также означает, что я могу эффективно обрабатывать любые изменения json для определенного объекта, и мне не нужно беспокоиться обо всем, что касается объекта. Это ни в коем случае не идеальное решение, но оно неплохо работает по результатам нашего модульного и интеграционного тестирования.

tmesser
источник
4

Предположим, что настройка autofac выглядит следующим образом:

public class AutofacContractResolver : DefaultContractResolver
{
    private readonly IContainer _container;

    public AutofacContractResolver(IContainer container)
    {
        _container = container;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);

        // use Autofac to create types that have been registered with it
        if (_container.IsRegistered(objectType))
        {
           contract.DefaultCreator = () => _container.Resolve(objectType);
        }  

        return contract;
    }
}

Затем предположим, что ваш класс такой:

public class TaskController
{
    private readonly ITaskRepository _repository;
    private readonly ILogger _logger;

    public TaskController(ITaskRepository repository, ILogger logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public ITaskRepository Repository
    {
        get { return _repository; }
    }

    public ILogger Logger
    {
        get { return _logger; }
    }
}

Следовательно, использование резольвера при десериализации может быть таким:

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<TaskRepository>().As<ITaskRepository>();
builder.RegisterType<TaskController>();
builder.Register(c => new LogService(new DateTime(2000, 12, 12))).As<ILogger>();

IContainer container = builder.Build();

AutofacContractResolver contractResolver = new AutofacContractResolver(container);

string json = @"{
      'Logger': {
        'Level':'Debug'
      }
}";

// ITaskRespository and ILogger constructor parameters are injected by Autofac 
TaskController controller = JsonConvert.DeserializeObject<TaskController>(json, new JsonSerializerSettings
{
    ContractResolver = contractResolver
});

Console.WriteLine(controller.Repository.GetType().Name);

Вы можете увидеть более подробную информацию в http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm

О, мой бог
источник
Я буду голосовать за это как за лучшее решение. В наши дни DI так широко используется веб-разработчиками на C #, и он отлично подходит в качестве централизованного места для обработки преобразования этих типов преобразователем.
appletwo
3

Никакой объект никогда не будет Чужим, поскольку все интерфейсы абстрактны по определению.

Первоначально сериализованный у вас объект имел конкретный тип, реализующий абстрактный интерфейс. Вам нужен такой же бетон класс оживил сериализованные данные.

Полученный объект будет иметь какой - то тип , который реализует абстрактный интерфейс , которого вы ищете.

Из документации следует, что можно использовать

(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));

при десериализации сообщить JSON.NET о конкретном типе.

Шон Кинси
источник
Это именно тот пост, который я имел в виду более года назад. Единственное важное предложение (написание кастомных преобразователей) не совсем выполнимо с масштабом, который я вынужден учитывать. JSON.NET сильно изменился за прошедший год. Я прекрасно понимаю различие между классом и интерфейсом, но C # также поддерживает неявные преобразования интерфейса в объект, реализующий интерфейс в отношении типизации. По сути, я спрашиваю, есть ли способ сообщить JSON.NET, какой объект будет реализовывать этот интерфейс.
tmesser
Все это было в ответе, на который я вам указал. Убедитесь, что есть _typeсвойство, указывающее , какой конкретный тип использовать.
Шон Кинси
И я сильно сомневаюсь, что C # поддерживает какое-либо «неявное» приведение типов из переменной, объявленной как интерфейс, к конкретному типу без каких-либо подсказок.
Шон Кинси
Если я не прочитал это неправильно, свойство _type должно было быть в JSON для сериализации. Это отлично работает, если вы десериализуете только то, что уже сериализовали, но это не то, что здесь происходит. Я беру JSON с ряда сайтов, которые не будут следовать этому стандарту.
tmesser
@YYY - Контролируете ли вы как сериализацию, так и десериализацию из исходного JSON? Потому что в конечном итоге вам нужно будет либо встроить конкретный тип в сериализованный JSON в качестве подсказки для использования при десериализации, либо вам нужно будет использовать какую-то модель try / parse, которая обнаруживает / пытается обнаружить конкретный тип во время выполнения и вызвать соответствующий десериализатор.
mcw
3

Мое решение этого, которое мне нравится, потому что оно довольно общее, заключается в следующем:

/// <summary>
/// Automagically convert known interfaces to (specific) concrete classes on deserialisation
/// </summary>
public class WithMocksJsonConverter : JsonConverter
{
    /// <summary>
    /// The interfaces I know how to instantiate mapped to the classes with which I shall instantiate them, as a Dictionary.
    /// </summary>
    private readonly Dictionary<Type,Type> conversions = new Dictionary<Type,Type>() { 
        { typeof(IOne), typeof(MockOne) },
        { typeof(ITwo), typeof(MockTwo) },
        { typeof(IThree), typeof(MockThree) },
        { typeof(IFour), typeof(MockFour) }
    };

    /// <summary>
    /// Can I convert an object of this type?
    /// </summary>
    /// <param name="objectType">The type under consideration</param>
    /// <returns>True if I can convert the type under consideration, else false.</returns>
    public override bool CanConvert(Type objectType)
    {
        return conversions.Keys.Contains(objectType);
    }

    /// <summary>
    /// Attempt to read an object of the specified type from this reader.
    /// </summary>
    /// <param name="reader">The reader from which I read.</param>
    /// <param name="objectType">The type of object I'm trying to read, anticipated to be one I can convert.</param>
    /// <param name="existingValue">The existing value of the object being read.</param>
    /// <param name="serializer">The serializer invoking this request.</param>
    /// <returns>An object of the type into which I convert the specified objectType.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return serializer.Deserialize(reader, this.conversions[objectType]);
        }
        catch (Exception)
        {
            throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
        }
    }

    /// <summary>
    /// Not yet implemented.
    /// </summary>
    /// <param name="writer">The writer to which I would write.</param>
    /// <param name="value">The value I am attempting to write.</param>
    /// <param name="serializer">the serializer invoking this request.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

}

Очевидно, вы могли бы тривиально преобразовать его в еще более общий преобразователь, добавив конструктор, принимающий аргумент типа Dictionary <Type, Type>, с помощью которого можно создать экземпляр переменной экземпляра преобразования.

Саймон Брук
источник
3

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

Я решил создать прокси-класс во время выполнения, который будет обертывать объект, возвращаемый Newtonsoft.

Преимущество этого подхода состоит в том, что он не требует конкретной реализации класса и может автоматически обрабатывать любую глубину вложенных интерфейсов. Вы можете узнать об этом больше в моем блоге .

using Castle.DynamicProxy;
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;

namespace LL.Utilities.Std.Json
{
    public static class JObjectExtension
    {
        private static ProxyGenerator _generator = new ProxyGenerator();

        public static dynamic toProxy(this JObject targetObject, Type interfaceType) 
        {
            return _generator.CreateInterfaceProxyWithoutTarget(interfaceType, new JObjectInterceptor(targetObject));
        }

        public static InterfaceType toProxy<InterfaceType>(this JObject targetObject)
        {

            return toProxy(targetObject, typeof(InterfaceType));
        }
    }

    [Serializable]
    public class JObjectInterceptor : IInterceptor
    {
        private JObject _target;

        public JObjectInterceptor(JObject target)
        {
            _target = target;
        }
        public void Intercept(IInvocation invocation)
        {

            var methodName = invocation.Method.Name;
            if(invocation.Method.IsSpecialName && methodName.StartsWith("get_"))
            {
                var returnType = invocation.Method.ReturnType;
                methodName = methodName.Substring(4);

                if (_target == null || _target[methodName] == null)
                {
                    if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                    {

                        invocation.ReturnValue = null;
                        return;
                    }

                }

                if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                {
                    invocation.ReturnValue = _target[methodName].ToObject(returnType);
                }
                else
                {
                    invocation.ReturnValue = ((JObject)_target[methodName]).toProxy(returnType);
                }
            }
            else
            {
                throw new NotImplementedException("Only get accessors are implemented in proxy");
            }

        }
    }



}

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

var jObj = JObject.Parse(input);
InterfaceType proxyObject = jObj.toProxy<InterfaceType>();
пенистый
источник
Спасибо! Это единственный ответ, который должным образом поддерживает динамическую типизацию (утиную типизацию), не накладывая ограничений на входящий json.
Филип Питтл,
Нет проблем. Я был немного удивлен, увидев, что там ничего нет. По сравнению с исходным примером он немного изменился, поэтому я решил поделиться кодом. github.com/sudsy/JsonDuckTyper . Я также опубликовал его на nuget как JsonDuckTyper. Если вы обнаружите, что хотите улучшить его, просто пришлите мне PR, и я буду счастлив помочь.
Sudsy
Когда я искал решение в этой области, я также наткнулся на github.com/ekonbenefits/impromptu-interface . В моем случае это не работает, так как не поддерживает ядро ​​dotnet 1.0, но может сработать для вас.
Sudsy
Я пробовал использовать Impromptu Interface, но Json.Net не был доволен выполнением PopulateObjectпрокси-сервера, созданного Impromptu Interface. К сожалению, я отказался от Duck Typing - просто было проще создать собственный сериализатор контрактов Json, который использовал отражение, чтобы найти существующую реализацию запрошенного интерфейса и использовать ее.
Филип Питтл,
1

Используйте этот JsonKnownTypes , это очень похожий способ использования, он просто добавляет дискриминатор в json:

[JsonConverter(typeof(JsonKnownTypeConverter<Interface1>))]
[JsonKnownType(typeof(MyClass), "myClass")]
public interface Interface1
{  }
public class MyClass : Interface1
{
    public string Something;
}

Теперь, когда вы сериализуете объект в json, он будет добавлен "$type"с помощью"myClass" значением и будет использоваться для десериализации

Json:

{"Something":"something", "$type":"derived"}
Дмитрий
источник
0

Моим решением были добавлены элементы интерфейса в конструктор.

public class Customer: ICustomer{
     public Customer(Details details){
          Details = details;
     }

     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {get; set;}
}
Хорхе Сантос Нил
источник