Estoy tratando de configurar un lector que tome objetos JSON de varios sitios web (piense en el raspado de información) y los traduzca en objetos C #. Actualmente estoy usando JSON.NET para el proceso de deserialización. El problema con el que me encuentro es que no sabe cómo manejar las propiedades de nivel de interfaz en una clase. Entonces algo de la naturaleza:
public IThingy Thing
Producirá el error:
No se pudo crear una instancia de tipo IThingy. El tipo es una interfaz o clase abstracta y no se puede instanciar.
Es relativamente importante que sea un Ithingy en lugar de un Thingy, ya que el código en el que estoy trabajando se considera confidencial y las pruebas unitarias son muy importantes. La burla de objetos para scripts de prueba atómica no es posible con objetos completos como Thingy. Deben ser una interfaz.
He estado estudiando detenidamente la documentación de JSON.NET durante un tiempo, y las preguntas que pude encontrar en este sitio relacionadas con esto son de hace más de un año. ¿Alguna ayuda?
Además, si es importante, mi aplicación está escrita en .NET 4.0.
Respuestas:
@SamualDavis proporcionó una gran solución en una pregunta relacionada , que resumiré aquí.
Si tiene que deserializar una secuencia JSON en una clase concreta que tiene propiedades de interfaz, ¡puede incluir las clases concretas como parámetros para un constructor para la clase! El deserializador NewtonSoft es lo suficientemente inteligente como para darse cuenta de que necesita usar esas clases concretas para deserializar las propiedades.
Aquí hay un ejemplo:
fuente
[JsonConstructor]
atributo(Copiado de esta pregunta )
En los casos en que no he tenido control sobre el JSON entrante (y, por lo tanto, no puedo asegurar que incluya una propiedad $ type), he escrito un convertidor personalizado que solo le permite especificar explícitamente el tipo concreto:
Esto solo usa la implementación predeterminada del serializador de Json.Net mientras especifica explícitamente el tipo concreto.
Una descripción general está disponible en esta publicación de blog . El código fuente está abajo:
fuente
ConcreteListTypeConverter<TInterface, TImplementation>
para manejar miembros de tipo de claseIList<TInterface>
.concreteTypeConverter
embargo, podría ser mejor tener el código real en la pregunta.ConcreteListTypeConverter<TInterface, TImplementation>
implementación?¿Por qué usar un convertidor? Hay una funcionalidad nativa
Newtonsoft.Json
para resolver este problema exacto:Establecer
TypeNameHandling
en elJsonSerializerSettings
queTypeNameHandling.Auto
Esto colocará cada tipo en el json, que no se considera una instancia concreta de un tipo, sino una interfaz o una clase abstracta.
Asegúrese de estar utilizando la misma configuración para la serialización y deserialización .
Lo probé y funciona de maravilla, incluso con listas.
Resultados de búsqueda Resultado web con enlaces a sitios
⚠️ ADVERTENCIA :
Solo use esto para json de una fuente conocida y confiable. El usuario snipsnipsnip mencionó correctamente que esto es de hecho una vulnerabilidad.
Consulte CA2328 y SCS0028 para obtener más información.
Fuente y una implementación manual alternativa: Code Inside Blog
fuente
Para habilitar la deserialización de múltiples implementaciones de interfaces, puede usar JsonConverter, pero no a través de un atributo:
DTOJsonConverter asigna cada interfaz con una implementación concreta:
DTOJsonConverter solo se requiere para el deserializador. El proceso de serialización no ha cambiado. El objeto Json no necesita incrustar nombres de tipos concretos.
Esta publicación SO ofrece la misma solución un paso más allá con un JsonConverter genérico.
fuente
FullName
s cuando solo puedes comparar tipos directamente?Use esta clase, para asignar el tipo abstracto al tipo real:
... y cuando se deserializa:
fuente
where TReal : TAbstract
para asegurarse de que puede emitir el tipowhere TReal : class, TAbstract, new()
.Nicholas Westby proporcionó una gran solución en un artículo impresionante .
Si desea Deserializar JSON a una de las muchas clases posibles que implementan una interfaz como esa:
Puede usar un convertidor JSON personalizado:
Y deberá decorar la propiedad "Profesión" con un atributo JsonConverter para que sepa que debe usar su convertidor personalizado:
Y luego, puede emitir su clase con una interfaz:
fuente
Dos cosas que podrías probar:
Implemente un modelo de prueba / análisis:
O, si puede hacerlo en su modelo de objetos, implemente una clase base concreta entre IPerson y sus objetos de hoja, y deserialícela.
El primero puede fallar potencialmente en tiempo de ejecución, el segundo requiere cambios en su modelo de objetos y homogeneiza la salida al mínimo común denominador.
fuente
Encontré esto útil. Tú también podrías.
Ejemplo de uso
Convertidor de creación personalizada
Documentación de Json.NET
fuente
Para aquellos que puedan tener curiosidad sobre el ConcreteListTypeConverter al que hizo referencia Oliver, aquí está mi intento:
fuente
CanConvert(Type objectType) { return true;}
. Parece hacky, ¿cómo es exactamente esto útil? Puedo estar equivocado, pero ¿no es eso como decirle a un luchador más pequeño e inexperto que van a ganar la pelea sin importar el oponente?Por lo que vale, terminé teniendo que manejar esto yo mismo en su mayor parte. Cada objeto tiene un método Deserialize (string jsonStream) . Algunos fragmentos de ella:
En este caso, el nuevo Thingy (string) es un constructor que llamará al método Deserialize (string jsonStream) del tipo concreto apropiado. Este esquema continuará yendo hacia abajo y hacia abajo hasta llegar a los puntos base que json.NET puede manejar.
Y así sucesivamente. Esta configuración me permitió dar configuraciones json.NET que puede manejar sin tener que refactorizar una gran parte de la biblioteca o usar modelos difíciles de probar / analizar que habrían empantanado toda nuestra biblioteca debido a la cantidad de objetos involucrados. También significa que puedo manejar efectivamente cualquier cambio de json en un objeto específico, y no tengo que preocuparme por todo lo que toca ese objeto. De ninguna manera es la solución ideal, pero funciona bastante bien desde nuestra unidad y las pruebas de integración.
fuente
Suponga una configuración de autofac como la siguiente:
Entonces, suponga que su clase es así:
Por lo tanto, el uso del solucionador en la deserialización podría ser como:
Puede ver más detalles en http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm
fuente
Ningún objeto será siempre sea un IThingy como interfaces son por definición abstracta.
El objeto que tiene que primero se serializó fue de algún tipo concreto , implementando la interfaz abstracta . Necesita que esta misma clase concreta reviva los datos serializados.
El objeto resultante será de algún tipo que implemente la interfaz abstracta que está buscando.
De la documentación se deduce que puede usar
al deserializar para informar a JSON.NET sobre el tipo concreto.
fuente
_type
propiedad que indique el tipo de concreto a usar.Mi solución a esta, que me gusta porque es muy general, es la siguiente:
}
Obviamente y trivialmente podría convertirlo en un convertidor aún más general al agregar un constructor que tomara un argumento de tipo Diccionario <Tipo, Tipo> con el que instanciar la variable de instancia de conversiones.
fuente
Varios años después y tuve un problema similar. En mi caso, había interfaces muy anidadas y una preferencia por generar las clases concretas en tiempo de ejecución para que funcionara con una clase genérica.
Decidí crear una clase proxy en tiempo de ejecución que envuelva el objeto devuelto por Newtonsoft.
La ventaja de este enfoque es que no requiere una implementación concreta de la clase y puede manejar cualquier profundidad de interfaces anidadas automáticamente. Puedes ver más sobre esto en mi blog .
Uso:
fuente
PopulateObject
proxy generado por Impromptu Interface. Desafortunadamente, dejé de usar Duck Typing: fue más fácil crear un serializador de contratos Json personalizado que utilizó la reflexión para encontrar una implementación existente de la interfaz solicitada y usarla.Use este JsonKnownTypes , es una forma muy similar de usar, solo agrega discriminador a json:
Ahora, cuando serialice el objeto en json se agregará
"$type"
con"myClass"
valor y se usará para deserializarJson
fuente
A mi solución se le agregaron los elementos de la interfaz en el constructor.
fuente