c# - Newtonsoft.JSON no puede convertir el modelo con el atributo TypeConverter
serialization json.net (3)
Tengo una aplicación C # MVC que almacena datos como cadenas JSON en un documento XML y también en tablas de base de datos MySQL.
Recientemente recibí el requisito de almacenar cadenas JSON en los campos de la base de datos MySQL , para convertirlas en objetos C # a través de Newtonsoft.Json , por lo que decidí implementar un TypeConverter para convertir cadenas JSON en modelos C # personalizados.
Desafortunadamente, no puedo usar el siguiente comando en ninguna parte de mi solución para deserializar mis cadenas JSON cuando el atributo TypeConverter se agrega a mi Modelo C # :
JsonConvert.DeserializeObject<Foo>(json);
La eliminación del atributo resuelve el problema, sin embargo, esto me impide convertir los campos de MySQL DB en objetos C # personalizados.
Aquí está mi modelo C # con el atributo TypeConverter agregado:
using System.ComponentModel;
[TypeConverter(typeof(FooConverter))]
public class Foo
{
public bool a { get; set; }
public bool b { get; set; }
public bool c { get; set; }
public Foo(){}
}
Aquí está mi clase TypeConverter :
using Newtonsoft.Json;
using System;
using System.ComponentModel;
public class FooConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
string s = value.ToString().Replace("//","");
Foo f = JsonConvert.DeserializeObject<Foo>(s);
return f;
}
return base.ConvertFrom(context, culture, value);
}
}
}
Tan pronto como agrego el atributo a la clase Foo, recibo el siguiente error:
No se puede deserializar el objeto JSON actual (por ejemplo, {"nombre": "valor"}) en el tipo ''Models.Foo'' porque el tipo requiere un valor de cadena JSON para deserializar correctamente.
Para corregir este error, cambie el JSON a un valor de cadena JSON o cambie el tipo deserializado para que sea un tipo .NET normal (por ejemplo, no un tipo primitivo como un entero, no un tipo de colección como una matriz o Lista) que pueda deserializarse de un objeto JSON. JsonObjectAttribute también se puede agregar al tipo para forzarlo a deserializarse de un objeto JSON.
Estoy usando la siguiente cadena (que funciona perfectamente sin agregar el atributo TypeConverter):
"{/"Foo/":{/"a/":true,/"b/":false,/"c/":false}}"
No estoy seguro de lo que está pasando aquí, ¿alguna idea?
¡¡¡Muchas gracias!!!
ACTUALIZAR
He descubierto que también tengo problemas con las acciones en los controladores API MVC que aceptan la clase de prueba con Foo como una propiedad o en los controladores que aceptan Foo como un objeto cuando el atributo TypeConverter se agrega a la clase Foo.
Aquí hay un ejemplo de un controlador de prueba que tiene problemas:
public class TestController : ApiController
{
[AcceptVerbs("POST", "GET")]
public void PostTestClass(TestClass t)
{
// Returns null when TypeConverter attribute is added to the Foo Class
return t.Foo;
}
AcceptVerbs("POST", "GET")]
public void PostFooObj(Foo f)
{
// Returns null when TypeConverter attribute is added to the Foo Class
return f;
}
}
El TypeConverter puede estar causando problemas al anular el enlace del modelo WebAPI y devuelve nulo cuando cualquiera de las acciones anteriores recibe JSON a través de AJAX con la siguiente estructura:
// eg. PostTestClass(TestClass T)
{''Foo'': {''a'': false,''b'': true,''c'': false}};
// eg. PostFooObj(Foo f)
{''a'': false,''b'': true,''c'': false}
Cuando el atributo TypeConverter se agrega a la clase Foo, se llama al siguiente método en la clase FooConverter TypeConverter tan pronto como se encuentra la ruta:
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
El método ConvertFrom en FooConverter TypeController NO es llamado por la acción de ApiController, que puede ser la causa del problema.
Una vez más, es una situación similar, donde las acciones de los controladores funcionarán bien sin el atributo TypeConverter.
¡Cualquier ayuda adicional es muy apreciada!
Muchas gracias.
Hay algunas cosas pasando aquí.
Primero, un problema preliminar: incluso sin ningún
TypeConverter
aplicado, su JSON no corresponde a su clase
Foo
, corresponde a alguna clase de contenedor que contiene una propiedad
Foo
, por ejemplo:
public class TestClass
{
public Foo Foo { get; set; }
}
Es decir, dada su cadena JSON, lo siguiente no funcionará:
var json = "{/"Foo/":{/"a/":true,/"b/":false,/"c/":false}}";
var foo = JsonConvert.DeserializeObject<Foo>(json);
Pero lo siguiente será:
var test = JsonConvert.DeserializeObject<TestClass>(json);
Sospecho que esto es simplemente un error en la pregunta, así que supondré que está buscando deserializar una clase que contenga una propiedad
Foo
.
El principal problema que está viendo es que Json.NET
intentará usar un
TypeConverter
si hay uno presente para convertir una clase que se serializará en una cadena JSON
.
De los
docs
:
Tipos primitivos
.Net:
TypeConverter
(convertible a String)
JSON: String
Pero en su JSON,
Foo
no es una cadena JSON, es un
objeto
JSON, por lo que la deserialización falla una vez que se aplica el convertidor de tipos.
Una cadena incrustada se vería así:
{"Foo":"{/"a/":true,/"b/":false,/"c/":false}"}
Observe cómo se han escapado todas las citas.
E incluso si cambió su formato JSON para que los objetos
Foo
coincidan con esto, su deserialización aún fallaría ya que
TypeConverter
y Json.NET intentan llamarse recursivamente.
Por lo tanto, lo que debe hacer es deshabilitar globalmente el uso de
TypeConverter
por parte de Json.NET y recurrir a la serialización predeterminada mientras se mantiene el uso de
TypeConverter
en todas las demás situaciones.
Esto es un poco complicado ya que no hay
un atributo Json.NET
que pueda aplicar para deshabilitar el uso de convertidores de tipo, en su lugar necesita un
JsonConverter
contrato especial más un
JsonConverter
especial para usarlo:
public class NoTypeConverterJsonConverter<T> : JsonConverter
{
static readonly IContractResolver resolver = new NoTypeConverterContractResolver();
class NoTypeConverterContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (typeof(T).IsAssignableFrom(objectType))
{
var contract = this.CreateObjectContract(objectType);
contract.Converter = null; // Also null out the converter to prevent infinite recursion.
return contract;
}
return base.CreateContract(objectType);
}
}
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Deserialize(reader, objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Serialize(writer, value);
}
}
Y úsalo como:
[TypeConverter(typeof(FooConverter))]
[JsonConverter(typeof(NoTypeConverterJsonConverter<Foo>))]
public class Foo
{
public bool a { get; set; }
public bool b { get; set; }
public bool c { get; set; }
public Foo() { }
}
public class FooConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
string s = value.ToString();
//s = s.Replace("//", "");
Foo f = JsonConvert.DeserializeObject<Foo>(s);
return f;
}
return base.ConvertFrom(context, culture, value);
}
}
Ejemplo de fiddle .
Finalmente, probablemente también debería implementar el método
ConvertTo
en su convertidor de tipos, consulte
Cómo: Implementar un convertidor de tipos
.
La manera fácil de evitar este comportamiento es eliminar OR de la verificación de conversión, es decir, eliminar || destinationType == typeof (cadena)
Un ejemplo a continuación ...
public class DepartmentBindModelConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(DepartmentViewModel); // Removed || destinationType == typeof(string), to allow newtonsoft json convert model with typeconverter attribute
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value == null)
return null;
if (destinationType == typeof(DepartmentViewModel) && value is DepartmentBindModel)
{
var department = (DepartmentBindModel) value;
return new DepartmentViewModel
{
Id = department.Id,
Name = department.Name,
GroupName = department.GroupName,
ReturnUrl = department.ReturnUrl
};
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
Si tiene una estructura, no una clase, la respuesta aceptada seguirá en una recursión infinita cuando intente (des) serializar
Nullable<Foo>
.
Para evitar modificar CreateContract de la siguiente manera:
protected override JsonContract CreateContract(Type objectType)
{
if (typeof(T).IsAssignableFrom(objectType)
|| Nullable.GetUnderlyingType(objectType) != null && typeof(T).IsAssignableFrom(Nullable.GetUnderlyingType(objectType)))
{
var contract = this.CreateObjectContract(objectType);
contract.Converter = null; // Also null out the converter to prevent infinite recursion.
return contract;
}
return base.CreateContract(objectType);
}