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.
Respostas:
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:
fonte
[JsonConstructor]
atributo(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:
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:
fonte
ConcreteListTypeConverter<TInterface, TImplementation>
para lidar com membros da classe do tipoIList<TInterface>
.concreteTypeConverter
na pergunta.ConcreteListTypeConverter<TInterface, TImplementation>
implementação?Por que usar um conversor? Há uma funcionalidade nativa
Newtonsoft.Json
para resolver esse problema exato:Situado
TypeNameHandling
noJsonSerializerSettings
aTypeNameHandling.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
fonte
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:
O DTOJsonConverter mapeia cada interface com uma implementação concreta:
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.
fonte
FullName
s quando pode apenas comparar tipos diretamente?Use esta classe, para mapear o tipo abstrato para o tipo real:
... e quando desserializar:
fonte
where TReal : TAbstract
para garantir que ela possa ser convertida para o tipo #where TReal : class, TAbstract, new()
.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:
Você pode usar um conversor JSON customizado:
E você precisará decorar a propriedade "Profissão" com um atributo JsonConverter para informá-lo sobre o uso do seu conversor personalizado:
E então, você pode transmitir sua classe com uma interface:
fonte
Duas coisas que você pode tentar:
Implemente um modelo try / parse:
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.
fonte
Achei isso útil. Você pode também.
Exemplo de uso
Conversor de criação personalizado
Documentação do Json.NET
fonte
Para aqueles que podem estar curiosos sobre o ConcreteListTypeConverter que foi referenciado por Oliver, aqui está minha tentativa:
fonte
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?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:
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.
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.
fonte
Suponha uma configuração de autofac como a seguinte:
Então, suponha que sua classe seja assim:
Portanto, o uso do resolvedor na desserialização pode ser como:
Você pode ver mais detalhes em http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm
fonte
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
ao desserializar para informar o JSON.NET sobre o tipo concreto.
fonte
_type
propriedade que sinalize o tipo de concreto a ser usado.Minha solução para este, que eu gosto por ser bem geral, é a seguinte:
}
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.
fonte
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 .
Uso:
fonte
PopulateObject
proxy 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.Use este JsonKnownTypes , é uma maneira muito semelhante, basta adicionar discriminador ao json:
Agora, quando você serializar um objeto no json, será adicionado
"$type"
com"myClass"
valor e será usado para desserializarJson:
fonte
Minha solução foi adicionada aos elementos da interface no construtor.
fonte