2015-07-09 12 views
8

Ho un'applicazione C# MVC che memorizza i dati come stringhe JSON in un documento XML e anche in tabelle DB MySQL.Newtonsoft.JSON non può convertire il modello con l'attributo TypeConverter

Recentemente ho ricevuto l'obbligo di memorizzare stringhe JSON in campi di database MySQL, per essere convertito in C# oggetti tramite Newtonsoft.Json, così ho deciso di implementare un TypeConverter per convertire le stringhe JSON in personalizzati C# Models.

purtroppo non posso utilizzare il seguente comando in qualsiasi parte mia soluzione per deserializzare le mie corde JSON quando l'attributo TypeConverter viene aggiunto al mio C# Modello:

JsonConvert.DeserializeObject<Foo>(json); 

Rimozione l'attributo risolvere il problema tuttavia questo mi impedisce dalla conversione dei campi DB MySQL in oggetti C# personalizzati.

Ecco il mio C# modello con l'attributo TypeConverter aggiunto:

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

Ecco il mio TypeConverter Classe:

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

Non appena aggiungo l'attributo al Foo Classe I riceve il seguente errore:

Impossibile deserializzare l'oggetto JSON corrente (ad es. {"name": "value"}) nel tipo "Models.Foo" perché il tipo richiede un valore di stringa JSON per deserializzare correttamente.

Per correggere l'errore sia modificare il JSON per un valore di stringa JSON o modificare il tipo deserializzare in modo che sia un tipo normale NET (ad esempio non un tipo di base come numero intero, non un tipo di insieme come un array o Elenco) che può essere deserializzato da un oggetto JSON. JSONObjectAttribute può anche essere aggiunto al tipo per forzarlo a deserializzare da un oggetto JSON.

Sto usando la seguente stringa (che funziona perfettamente senza aggiungere l'attributo TypeConverter):

"{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}" 
Non

sicuro di quello che sta succedendo qui, tutte le idee?

Molte grazie !!!

UPDATE

ho hanno scoperto che anche io ho problemi con azioni su MVC API Controller che accettano classe di test con Foo come una proprietà o sui controller che accettano Foo come oggetto quando l'attributo TypeConverterviene aggiunto alla classe Foo.

Ecco un esempio di un controller di test che ha problemi:

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

La TypeConverter potrebbe causare problemi imperative modello WebAPI rilegatura e restituisce null quando entrambe le azioni sopra riceve JSON tramite AJAX con la seguente struttura:

// eg. PostTestClass(TestClass T) 
{'Foo': {'a': false,'b': true,'c': false}}; 

// eg. PostFooObj(Foo f) 
{'a': false,'b': true,'c': false} 

quando l'attributo TypeConverter viene aggiunto alla classe Foo, il seguente metodo della classe FooConverter TypeConverter è chiamato non appena il percorso si trova:

public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType) 
    { 
     if (sourceType == typeof(string)) 
     { 
      return true; 
     } 
     return base.CanConvertFrom(context, sourceType); 
    } 

Il metodo ConvertFrom sul TypeController FooConverter NON viene chiamato dall'azione di ApiController, che potrebbe essere la causa del problema.

Ancora una situazione simile, in cui le azioni del controller funzioneranno correttamente senza l'attributo TypeConverter.

Qualsiasi ulteriore aiuto molto apprezzato !!

Molte grazie.

+0

è la stringa JSON corretta su ciò che è stato mostrato "" {\ "Foo \": ...} "'? – kaveman

+0

Sì, sei corretto, il JSON non cambia nella mia soluzione di test. Tutto quello che sto cambiando è l'aggiunta/rimozione dell'attributo TypeConverter. Grazie – Steve

+2

Sono curioso di sapere se puoi usare una struttura JSON più simile a "" {\ "a \": true, \ "b \": false, \ "c \": false} "" ad es. senza il wrapping '" {\ "Foo \": ...} "' oggetto. Questo JSON può essere deserializzato direttamente usando 'JsonConvert.DeserializeObject (json)' – kaveman

risposta

13

Ci sono alcune cose che succedono qui. In primo luogo, una questione preliminare: anche senza TypeConverter applicato, il vostro JSON non corrisponde alla classe Foo, corrisponde a qualche classe contenitore che contiene una proprietà Foo, per esempio:

public class TestClass 
{ 
    public Foo Foo { get; set; } 
} 

Vale a dire data la stringa JSON, il seguente non funziona:

var json = "{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}"; 
var foo = JsonConvert.DeserializeObject<Foo>(json); 

Ma il seguente testamento:

var test = JsonConvert.DeserializeObject<TestClass>(json); 

Ho il sospetto che questo è semplicemente un errore nella domanda, quindi mi supporre si sta cercando di deserializzare una classe contiene una proprietà Foo.

Il problema principale che state vedendo è che Json.NET tenterà di utilizzare un TypeConverter se presente per convertire una classe ad essere serializzato in una stringa JSON. Dal docs:

Primitive Types

.Net: TypeConverter (convertible to String)
JSON: String

Ma nel tuo JSON, Foo non è una stringa JSON, si tratta di un JSON oggetto, in tal modo deserializzazione non riesce una volta che viene applicato il convertitore di tipi. Una stringa incorporata sarà simile a questa:

{"Foo":"{\"a\":true,\"b\":false,\"c\":false}"} 

Nota come tutte le virgolette sono state sfuggite. E anche se hai modificato il tuo formato JSON per gli oggetti in modo che corrispondessero a questo, la deserializzazione fallirebbe in quanto lo TypeConverter e Json.NET tentano di chiamarsi reciprocamente in modo ricorsivo.

Pertanto, è necessario disabilitare globalmente l'uso di TypeConverter da parte di Json.NET e tornare alla serializzazione predefinita mantenendo l'uso di TypeConverter in tutte le altre situazioni.Questo è un po 'complicato perché non c'è Json.NET attribute è possibile applicare a disabilitare l'uso di convertitori di tipi, invece hai bisogno di una speciale resolver contratto più uno speciale JsonConverter a fare uso di esso:

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

e usarlo come:

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

Esempio fiddle.

Infine, probabilmente si dovrebbe anche implementare il metodo ConvertTo nel convertitore di tipi, vedere How to: Implement a Type Converter.

+0

Fantastic, ha funzionato come un incantesimo per risolvere il mio problema. Hai ragione, ho fatto un errore con il JSON nella domanda mentre formattavo il codice per gli esempi di domande SO - Sto deserializzando una classe con Foo come una proprietà e talvolta anche l'intera classe Foo stessa. Purtroppo ho scoperto che ho anche problemi con le azioni su ** MVC API Controllers ** che accettano la ** Test Class con Foo come proprietà ** o su controller che accettano ** Foo come oggetto ** quando il ** L'attributo TypeConverter ** viene aggiunto alla classe Foo. Esaminerò brevemente un esempio, molte grazie fino a questo momento @dbc – Steve

+2

Se questa è la risposta, considera la marcatura come tale. – theMayer

+0

@theMayer: devo pubblicare il mio aggiornamento come una nuova domanda e contrassegnarlo come completato? Grazie – Steve

Problemi correlati