Interfaces de transmissão para desserialização no JSON.NET

128

Eu estou tentando configurar um leitor que irá receber objetos JSON de vários sites (pense em raspagem de informações) e os traduza em objetos C #. Atualmente, estou usando o JSON.NET para o processo de desserialização. O problema que estou enfrentando é que ele não sabe como lidar com propriedades no nível da interface em uma classe. Então, algo da natureza:

public IThingy Thing

Irá produzir o erro:

Não foi possível criar uma instância do tipo IThingy. Type é uma interface ou classe abstrata e não pode ser instanciado.

É relativamente importante que seja um IThingy em vez de um Thingy, pois o código em que estou trabalhando é considerado sensível e o teste de unidade é altamente importante. A zombaria de objetos para scripts de teste atômico não é possível com objetos de pleno direito, como o Thingy. Eles devem ser uma interface.

Estou pesquisando a documentação do JSON.NET há algum tempo e as perguntas que encontrei neste site relacionadas a isso são de mais de um ano atrás. Qualquer ajuda?

Além disso, se isso importa, meu aplicativo está escrito no .NET 4.0.

tmesser
fonte

Respostas:

115

O @SamualDavis forneceu uma ótima solução em uma pergunta relacionada , que resumirei aqui.

Se você precisar desserializar um fluxo JSON em uma classe concreta que possua propriedades de interface, poderá incluir as classes concretas como parâmetros em um construtor para a classe! O desserializador da NewtonSoft é inteligente o suficiente para descobrir que precisa usar essas classes concretas para desserializar as propriedades.

Aqui está um exemplo:

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; }
}
Mark Meuer
fonte
15
Como isso funcionaria com uma ICollection? ICollection <IGuest> Guests {get; set;}
DrSammyD
12
Ele funciona com ICollection <ConcreteClass>, portanto, ICollection <Guest> funciona. Assim como um FYI, você pode colocar o atributo [JsonConstructor] no seu construtor para que ele vai usar isso por padrão, se acontecer de você ter vários construtores
DrSammyD
6
Estou preso ao mesmo problema, no meu caso, tenho várias implementações da interface (no seu exemplo, a interface é ILocation), e daí se houver classes como MyLocation, VIPLocation, OrdinaryLocation. Como mapeá-los para a propriedade Location? Se você tem apenas uma implementação como MyLocation, é fácil, mas como fazer se houver várias implementações de ILocation?
Ather
10
Se você tiver mais de um construtor, poderá marcar seu construtor especial com o [JsonConstructor]atributo
Dr Rob Lang
26
Isso não está bem. O objetivo de usar interfaces é usar injeção de dependência, mas ao fazer isso com um parâmetro de tipo de objeto exigido pelo construtor, você estraga totalmente o ponto de ter uma interface como uma propriedade.
Jérôme MEVEL 29/08/16
57

(Copiado desta pergunta )

Nos casos em que não tenho controle sobre o JSON recebido (e, portanto, não posso garantir que ele inclua uma propriedade $ type), escrevi um conversor personalizado que apenas permite especificar explicitamente o tipo concreto:

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

Isso apenas usa a implementação do serializador padrão do Json.Net enquanto especifica explicitamente o tipo concreto.

Uma visão geral está disponível nesta postagem do blog . O código fonte está abaixo:

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);
    }
}
Steve Greatrex
fonte
11
Eu realmente gosto dessa abordagem e a apliquei ao nosso próprio projeto. Eu até adicionei um ConcreteListTypeConverter<TInterface, TImplementation>para lidar com membros da classe do tipo IList<TInterface>.
6124 Oliver
3
Isso é um ótimo código. Talvez seja melhor ter o código real concreteTypeConverterna pergunta.
Chris
2
@ Oliver - Você pode postar sua ConcreteListTypeConverter<TInterface, TImplementation>implementação?
Michael
2
E se você tiver dois implementadores de ISomething?
precisa saber é o seguinte
56

Por que usar um conversor? Há uma funcionalidade nativa Newtonsoft.Jsonpara resolver esse problema exato:

Situado TypeNameHandlingno JsonSerializerSettingsaTypeNameHandling.Auto

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

Isso colocará todo tipo no json, que não é mantido como uma instância concreta de um tipo, mas como uma interface ou uma classe abstrata.

Verifique se você está usando as mesmas configurações para serialização e desserialização .

Eu testei e funciona como um encanto, mesmo com listas.

Resultados da pesquisa Resultado da Web com links de sites

⚠️ AVISO :

Use isso apenas para json de uma fonte conhecida e confiável. O usuário snipsnipsnip mencionou corretamente que isso é realmente uma indenização.

Consulte CA2328 e SCS0028 para obter mais informações.


Fonte e uma implementação manual alternativa: Code Inside Blog

Mafii
fonte
3
Perfeito, isso me ajudou para uma rápida e suja clone profundo ( stackoverflow.com/questions/78536/deep-cloning-objects )
Compufreak
1
@Shimmy Objects: "Inclua o nome do tipo .NET ao serializar em uma estrutura de objeto JSON." Automático: inclua o nome do tipo .NET quando o tipo do objeto que está sendo serializado não for o mesmo que o tipo declarado. Observe que isso não inclui o objeto serializado raiz por padrão. Para incluir o nome do tipo de objeto raiz no JSON, você deve especificar um objeto de tipo raiz com SerializeObject (Object, Type, JsonSerializerSettings) ou Serialize (JsonWriter, Object, Type). "Fonte: newtonsoft.com/json/help/html/…
Mafii
4
Eu apenas tentei isso na desserialização e não funciona. A linha de assunto desta pergunta Stack Overflow é, "Casting as interfaces para desserialização em JSON.NET"
Justin Russo
3
@JustinRusso ele só funciona quando o JSON tem sido serializado com a mesma configuração
Mafii
3
Voto positivo para a solução rápida, se não suja. Se você está apenas serializando configurações, isso funciona. É melhor do que parar o desenvolvimento para construir conversores e certamente é melhor do que decorar todas as propriedades injetadas. serializer.TypeNameHandling = TypeNameHandling.Auto; JsonConvert.DefaultSettings (). TypeNameHandling = TypeNameHandling.Auto;
Sean Anderson
39

Para ativar a desserialização de várias implementações de interfaces, você pode usar o JsonConverter, mas não através de um atributo:

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

O DTOJsonConverter mapeia cada interface com uma implementação concreta:

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

O DTOJsonConverter é necessário apenas para o desserializador. O processo de serialização é inalterado. O objeto Json não precisa incorporar nomes de tipos concretos.

Este post SO oferece a mesma solução um passo adiante com um JsonConverter genérico.

Eric Boumendil
fonte
A chamada do método WriteJson para o serializador.Serialize não causaria um estouro de pilha, uma vez que chamar serialize no valor que está sendo serializado pelo conversor faria com que o método WriteJson do conversor fosse chamado novamente recursivamente?
Triynko 02/12/13
Não deveria, se o método CanConvert () retornar um resultado consistente.
precisa saber é o seguinte
3
Por que você está comparando FullNames quando pode apenas comparar tipos diretamente?
Alex Zhukovskiy
Apenas comparar tipos também é bom.
Eric Boumendil
23

Use esta classe, para mapear o tipo abstrato para o tipo real:

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

... e quando desserializar:

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

        JsonConvert.DeserializeObject(json, type, settings);
Gildor
fonte
1
Eu realmente gosto de uma boa resposta concisa que resolva meu problema. Não há necessidade de autofac ou qualquer coisa!
Ben Power
3
Vale a pena colocar isso na declaração da classe do conversor: where TReal : TAbstractpara garantir que ela possa ser convertida para o tipo #
Artemious 30/10/18
1
Um local mais completo pode estar where TReal : class, TAbstract, new().
Erik Philips
2
Também usei esse conversor com struct, acredito que "onde TReal: TAbstract" é suficiente. Obrigado a todos.
Gildor
2
Ouro! Maneira lisa de ir.
SwissCoder 27/05
12

Nicholas Westby forneceu uma ótima solução em um artigo incrível .

Se você deseja desserializar o JSON para uma das muitas classes possíveis que implementam uma interface como essa:

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#"
            }
        };
    }
}

Você pode usar um conversor JSON customizado:

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

E você precisará decorar a propriedade "Profissão" com um atributo JsonConverter para informá-lo sobre o uso do seu conversor personalizado:

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

E então, você pode transmitir sua classe com uma interface:

Person person = JsonConvert.DeserializeObject<Person>(jsonString);
A. Morel
fonte
8

Duas coisas que você pode tentar:

Implemente um modelo try / parse:

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

Ou, se você puder fazê-lo em seu modelo de objeto, implemente uma classe base concreta entre IPerson e seus objetos folha e desserialize para ela.

O primeiro pode potencialmente falhar no tempo de execução, o segundo requer alterações no modelo de objeto e homogeneiza a saída para o menor denominador comum.

mcw
fonte
Um modelo de tentativa / análise não é viável devido à escala com a qual tenho que trabalhar. Eu tenho que considerar um escopo de centenas de objetos base com ainda mais centenas de objetos stub / helper para representar objetos JSON incorporados que acontecem muito. Não está fora de questão alterar o modelo de objeto, mas o uso de uma classe base concreta nas propriedades nos tornaria incapazes de zombar de itens para testes de unidade? Ou estou entendendo isso de alguma forma?
tmesser
Você ainda pode implementar uma simulação do IPerson - observe que o tipo da propriedade Organisation.Owner ainda é o IPerson. Mas para a desserialização de um alvo arbitrário, você deve retornar um tipo concreto. Se você não possui a definição de tipo e não pode definir o conjunto mínimo de propriedades que seu código exigirá, seu último recurso é como uma bolsa de chave / valor. Usando seu comentário de exemplo do facebook - você pode postar em uma resposta como são as suas (uma ou várias) implementações de ILocation? Isso pode ajudar a levar as coisas adiante.
Mcw 25/04
Como a principal esperança é zombar, a interface ILocation é, na verdade, meramente uma fachada para o objeto concreto Location. Um exemplo rápido que acabei de criar seria algo assim ( pastebin.com/mWQtqGnB ) para a interface e este ( pastebin.com/TdJ6cqWV ) para o objeto concreto.
precisa saber é o seguinte
E para dar o próximo passo, este é um exemplo de como seria o IPage ( pastebin.com/iuGifQXp ) e Page ( pastebin.com/ebqLxzvm ). O problema, é claro, é que, embora a desserialização de Page geralmente funcione bem, ela engasga quando chega à propriedade ILocation.
precisa saber é o seguinte
Ok, então, pensando nos objetos que você está raspando e desserializando - geralmente os dados JSON são consistentes com uma única definição de classe concreta? O que significa (hipoteticamente) que você não encontraria "locais" com propriedades adicionais que tornariam o Local inadequado para uso como o tipo concreto para o objeto desserializado? Nesse caso, atribuir a propriedade ILocation da página a um "LocationConverter" deve funcionar. Se não, e é porque os dados JSON nem sempre estão em conformidade com uma estrutura rígida ou consistente (como ILocation), então (... continuação)
mcw
8

Achei isso útil. Você pode também.

Exemplo de uso

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

Conversor de criação personalizado

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

Documentação do Json.NET

Smiggleworth
fonte
1
Não é uma solução viável. Não aborda listas e leva a espalhar decoradores / anotações em todos os lugares.
Sean Anderson
5

Para aqueles que podem estar curiosos sobre o ConcreteListTypeConverter que foi referenciado por Oliver, aqui está minha tentativa:

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);
    }
}
Matt M
fonte
1
Estou confuso com o substituído CanConvert(Type objectType) { return true;}. Parece hacky, como exatamente isso é útil? Posso estar errado, mas não é como dizer a um lutador inexperiente menor que ele vencerá a luta, não importa o oponente?
Chef_Code 4/17
4

Pelo que vale a pena, acabei tendo que lidar com isso na maior parte do tempo. Cada objeto possui um método Deserialize (string jsonStream) . Alguns trechos:

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

Nesse caso, o novo Thingy (string) é um construtor que chamará o método Deserialize (string jsonStream) do tipo concreto apropriado. Esse esquema continuará descendo e descendo até chegar aos pontos de base que o json.NET pode manipular.

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

E assim por diante. Essa configuração me permitiu fornecer as configurações do json.NET que ele pode manipular sem ter que refatorar grande parte da própria biblioteca ou usar modelos difíceis de tentar / analisar que atolariam em toda a biblioteca devido ao número de objetos envolvidos. Isso também significa que eu posso lidar efetivamente com quaisquer alterações de json em um objeto específico e não preciso me preocupar com tudo o que o objeto toca. Não é de modo algum a solução ideal, mas funciona muito bem em nossos testes de unidade e integração.

tmesser
fonte
4

Suponha uma configuração de autofac como a seguinte:

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

Então, suponha que sua classe seja assim:

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

Portanto, o uso do resolvedor na desserialização pode ser como:

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

Você pode ver mais detalhes em http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm

AMD
fonte
Voto-a como a melhor solução. Atualmente, o DI tem sido amplamente utilizado pelos desenvolvedores do c # web, e isso se encaixa muito bem como um local centralizado para lidar com a conversão de tipos pelo resolvedor.
Appletwo
3

Nenhum objeto vai sempre ser um IThingy como interfaces são todos abstrato por definição.

O objeto que você teve que foi serializado pela primeira vez era de algum tipo concreto , implementando a interface abstrata . Você precisa ter essa mesma classe concreta para reviver os dados serializados.

O objeto resultante será de algum tipo que implementa a interface abstrata que você está procurando.

A partir da documentação , segue-se que você pode usar

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

ao desserializar para informar o JSON.NET sobre o tipo concreto.

Sean Kinsey
fonte
Esse é precisamente o post de mais de um ano atrás que eu estava me referindo. A única sugestão importante (escrever conversores personalizados) não é muito viável com a escala que sou forçado a considerar. O JSON.NET mudou muito neste ano. Entendo perfeitamente a distinção entre uma classe e uma interface, mas o C # também suporta conversões implícitas de uma interface para um objeto que implementa a interface em relação à digitação. Estou essencialmente perguntando se existe uma maneira de dizer ao JSON.NET qual objeto implementará essa interface.
tmesser
Estava tudo lá na resposta que eu apontei para você. Verifique se há uma _typepropriedade que sinalize o tipo de concreto a ser usado.
Sean Kinsey
E duvido fortemente que o C # suporte qualquer tipo de conversão de texto "implícita" de uma variável declarada como interface para um tipo concreto, sem nenhum tipo de dica.
Sean Kinsey
A menos que eu tenha lido errado, a propriedade _type deveria estar no JSON para ser serializada. Isso funciona bem se você estiver desserializando apenas o que já serializou, mas não é isso que está acontecendo aqui. Estou puxando JSON de vários sites que não seguirão esse padrão.
tmesser
@YYY - Você controla a serialização e a desserialização do JSON de origem? Como, em última análise, você precisará incorporar o tipo concreto no JSON serializado como uma dica para desserializar ou usar algum tipo de modelo de tentativa / análise que detecta / tenta detectar o tipo concreto em tempo de execução e chame o desserializador apropriado.
mcw
3

Minha solução para este, que eu gosto por ser bem geral, é a seguinte:

/// <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();
    }
}

}

Você poderia obviamente e trivialmente convertê-lo em um conversor ainda mais geral adicionando um construtor que utilizou um argumento do tipo Dictionary <Type, Type> com o qual instanciar a variável de instância de conversões.

Simon Brooke
fonte
3

Vários anos depois, tive um problema semelhante. No meu caso, havia interfaces fortemente aninhadas e uma preferência por gerar as classes concretas em tempo de execução, para que funcionasse com uma classe genérica.

Decidi criar uma classe proxy em tempo de execução que agrupa o objeto retornado pela Newtonsoft.

A vantagem dessa abordagem é que ela não requer uma implementação concreta da classe e pode lidar com qualquer profundidade de interfaces aninhadas automaticamente. Você pode ver mais sobre isso no meu blog .

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

        }
    }



}

Uso:

var jObj = JObject.Parse(input);
InterfaceType proxyObject = jObj.toProxy<InterfaceType>();
Sudsy
fonte
Obrigado! Esta é a única resposta que suporta corretamente a digitação dinâmica (digitação com patos) sem forçar restrições no json recebido.
precisa saber é o seguinte
Sem problemas. Fiquei um pouco surpreso ao ver que não havia nada lá fora. Ele mudou um pouco desde o exemplo original, então decidi compartilhar o código. github.com/sudsy/JsonDuckTyper . Também publiquei no nuget como JsonDuckTyper. Se achar que deseja aprimorá-lo, envie-me um PR e ficarei feliz em agradecer.
precisa saber é o seguinte
Quando eu estava procurando uma solução nessa área, me deparei com github.com/ekonbenefits/impromptu-interface também. Não funciona no meu caso, pois não suporta o dotnet core 1.0, mas pode funcionar para você.
Sudsy
Tentei com o Impromptu Interface, mas o Json.Net não estava feliz em fazer um PopulateObjectproxy gerado pelo Impromptu Interface. Infelizmente, desisti de usar o Duck Typing - era mais fácil criar um serializador de contrato Json personalizado que usava o reflexo para encontrar uma implementação existente da interface solicitada e usá-la.
Philip Pittle
1

Use este JsonKnownTypes , é uma maneira muito semelhante, basta adicionar discriminador ao json:

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

Agora, quando você serializar um objeto no json, será adicionado "$type"com "myClass"valor e será usado para desserializar

Json:

{"Something":"something", "$type":"derived"}
Dmitry
fonte
0

Minha solução foi adicionada aos elementos da interface no construtor.

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

     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {get; set;}
}
Jorge Santos Neill
fonte