2013-02-15 11 views
5

Possiedo un convertitore che desidero utilizzare solo durante la deserializzazione. Così ho impostato CanWrite su false, che funziona bene e tutto si serializza bene. La stringa Json contiene quindi un grafico oggetto all'interno del quale è presente una SantaClauseCollection con una matrice di elementi SantaClause e un tipo $ che indica che sono di tipo concreto SantaClause.JSON.Net non sta chiamando CanConvert per l'articolo da collezione?

Tuttavia, quando incontra una raccolta di SantaClaus durante la deserializzazione, non chiama mai CanConvert (ho un punto di interruzione e vedo SantaClausCollection, premete F5 per continuare, che dovrebbe quindi colpire nuovamente il punto di interruzione quando si incontra un oggetto nel collezione di SantaClaus, ma non è così). Non sta cercando di chiamare CanConvert quando arriva all'elemento SantaClaus. Senza nemmeno chiamare CanConvert per quell'elemento per verificare se il mio convertitore lo gestirà, cercherà invece di deserializzare esso stesso, che non funzionerà perché la classe non ha costruttori predefiniti e nessun costruttore con convenzioni di corrispondenza nome-proprietà:

Impossibile trovare un costruttore da utilizzare per il tipo SantaClaus. Una classe deve avere un costruttore predefinito, un costruttore con argomenti o un costruttore contrassegnato con l'attributo JsonConstructor.

capisco perché ottengo questo errore, ma il problema è che indica che Json.net cercato di deserializzare l'oggetto, invece di chiamare CanConvert per controllare e vedere se il mio convertitore voleva gestire la deserializzazione, invece.

Perché non è possibile chiamare CanConvert per ogni articolo della raccolta?

mio convertitore:

class SantaClaus2JsonConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof(SantaClaus);   
    } 

    /// <summary> 
    /// Deserializes a SantaClaus as a SantaClausEx which has a matching constructor that allows it to deserialize naturally. 
    /// </summary>  
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     return serializer.Deserialize<SantaClausEx>(reader); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     serializer.Serialize(writer, value); 
    } 


    public override bool CanRead 
    { 
     get 
     { 
      return true; 
     } 
    } 

    public override bool CanWrite 
    { 
     get 
     { 
      return false;//We only need this converter when reading. 
     } 
    } 

} 

SantaClausEx è solo eredita da SantaClaus per aggiungere un costruttore con un parametro rinominato per abbinare proprietà:

class SantaClaus //a third party class I can't modify 
{ 
    string Name {get;set;} 
    public SantaClaus(string santaClauseName) { this.Name = santaClauseName } 
} 

class SantaClausEx:SantaClaus 
{ 
    //provide a constructor with param names matching property names 
    public SantaClausEx(string name) : base(name) 
} 

Json.net non può deserializzare un SantaClaus, ma può deserializzare un SantaClauseEx.

Io uso quella classe SantaClauseEx ovunque e funziona perfettamente, ma volevo fare un convertitore per farlo automaticamente.

Questo è ciò che il JSON sembra per la raccolta:

SantaClausCollection: [ 
{ 
    $type: "SantaClaus, XMasClasses.NET20" 
    Name: "St. Bob" 
}, 
{ 
    $type: "SantaClaus, XMasClasses.NET20" 
    Name: "St. Jim" 
} 
] 
+0

Come stai effettivamente effettuando la chiamata di conversione? I metodi Serialize/Deserialize di solito contengono sovraccarichi che accettano convertitori da utilizzare se applicabile. Il tuo convertitore non verrà chiamato magicamente solo perché esiste, ma deve essere passato come uno di quei convertitori. –

risposta

1

Suppongo che si è aggiunto il convertitore a Converters raccolta in ambienti oggetto.

ho scritto semplice test con convertitore che funziona

public class SantaClausJsonTest 
{ 
    public SantaClausJsonTest() 
    { 
     Settings = new JsonSerializerSettings(); 
     Settings.TypeNameHandling = TypeNameHandling.Objects; 
     Settings.Converters.Add(new SantaClaus2JsonConverter()); 
    } 

    private JsonSerializerSettings Settings; 

    [Fact] 
    public void SerializeAndDeserialize() 
    { 
     var collection = new [] 
      { 
       new SantaClaus("St. Bob"), 
       new SantaClaus("St. Jim"), 
      }; 

     var serialized = JsonConvert.SerializeObject(collection, Settings); 

     Console.WriteLine(serialized); 
     Assert.False(string.IsNullOrEmpty(serialized)); 

     var deserialized = JsonConvert.DeserializeObject<SantaClaus[]>(serialized, Settings); 

     Console.WriteLine(deserialized.GetType().ToString()); 
     Assert.NotNull(deserialized); 
     Assert.True(deserialized.Any(a => a.Name == "St. Bob")); 
     Assert.True(deserialized.Any(a => a.Name == "St. Jim")); 
    } 
} 

public class SantaClaus 
{ 
    public SantaClaus(string santaClauseName) 
    { 
     Name = santaClauseName; 
    } 

    public string Name { get; private set; } 
} 

public class SantaClaus2JsonConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof(SantaClaus); 
    } 

    /// <summary> 
    /// Deserializes a SantaClaus as a SantaClausEx which has a matching constructor that allows it to deserialize naturally. 
    /// </summary>  
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     var name = string.Empty; 

     while (reader.Read()) 
     { 
      if (reader.TokenType == JsonToken.String && reader.Path.EndsWith("Name")) 
      { 
       name = reader.Value as string; 
      } 
      if (reader.TokenType == JsonToken.EndObject) 
      { 
       break; 
      } 
     } 

     return Activator.CreateInstance(objectType, name); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotSupportedException(); 
    } 


    public override bool CanRead 
    { 
     get 
     { 
      return true; 
     } 
    } 

    public override bool CanWrite 
    { 
     get 
     { 
      return false;//We only need this converter when reading. 
     } 
    } 
+0

Perfetto. Devo aspettare di assegnare la taglia fino a domani – AaronLS

+3

Attenzione. Specifica come specificare il tipo è necessario in DeserializeObject . Ho trovato che per gli oggetti JSON di primo livello, in seguito alla deserializzazione, non riesce a chiamare il convertitore a meno che non si codifichi esplicitamente il tipo come questo nel generico 'DeserializeObject 'sovraccarico del metodo. Ad esempio, se avessi una stringa JSON come "{" $ type ":" SantaClaus "," Name ":" anyname "}" e provassi a deserializzarla con DeserializeObject, creerebbe un oggetto SantaClause, ma non tramite il tuo convertitore Metodo ReadJson. Semplicemente istanzia il tipo e assegna la proprietà. Un bug, IMO. – Triynko

+0

@Triynko Buon punto. Penso che la mancanza del tipo generico sulla deserializzazione sia stata l'unica cosa che mi mancava nel mio tentativo iniziale. – AaronLS

1

ho avuto un problema simile deserializzazione oggetti ereditati da una classe base (simile a come è necessario deserializzare un oggetto SantaClauseEx ma sono tutti definiti come oggetti SantaClause). Il problema è in JSon.Net non essere in grado di identificare il sottotipo.

Vedi stackoverflow.com/questions/8030538/how-to-implement-custom-jsonconverter-in-json-net-to-deserialize-a-list-of-base

+0

Grazie. "non essendo in grado di identificare il sottotipo", il tipo nel JSON è '$ tipo:" SantaClaus, 'che va bene, perché il mio convertitore è per' 'CanConvert' di SantaClaus ... typeof (SantaClaus) 'e il mio convertitore gestisce la sua conversione a SantaClausEx. Non mi aspetto che Json.net determini magicamente il sottotipo. È un problema con l'ordine che Json.net stia cercando di creare un'istanza di SantaClaus prima di controllare CanConvert. Quindi credo che la soluzione che hai collegato funzionerà davvero intorno al problema poiché posso implementare la logica di creazione per il tipo di base invece di utilizzare il convertitore, lo testeremo – AaronLS

0

Una volta ho avuto la risposta di lavoro di Rudus, ho individuato il problema con il mio tentativo originale. È fantastico quando si ha un tipo senza costruttore predefinito, ma può mappare valori di proprietà a uno dei suoi altri costruttori ed è certamente più semplice per il mio caso specifico.

Se per qualche motivo hai davvero bisogno di qualcosa di simile a quello che stavo cercando di fare in origine quando crei un tipo diverso durante la deserializzazione, sono riuscito a farlo funzionare.

public class SantaClaus2JsonConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof(SantaClaus); 
    } 

    /// <summary> 
    /// Deserializes a SantaClaus as a SantaClausEx which has a matching constructor that allows it to deserialize naturally. 
    /// </summary>  
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     //temporarily switch off name handling so it ignores "SantaClaus" type when 
     //explicitely deserialize as SantaClausEx 
     //This could cause issues with nested types however in a more complicated object graph 
     var temp = serializer.TypeNameHandling; 
     serializer.TypeNameHandling = TypeNameHandling.None; 
     var desr = serializer.Deserialize<SantaClausEx>(reader); 
     serializer.TypeNameHandling = temp;//restore previous setting 

     return desr; 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { 
     throw new NotSupportedException(); 
    } 

    public override bool CanRead { get { return true; } } 

    public override bool CanWrite { get { false; } } //only for reading 

} 
Problemi correlati