2012-01-12 14 views
45

Quando serializzazione dati arbitrari via JSON.NET, qualsiasi proprietà che è null viene scritto il JSON comeserializzazione null in JSON.NET

"propertyName": null

Ciò è corretto, ovviamente.

Tuttavia, ho il requisito di tradurre automaticamente tutti i valori null nel valore vuoto predefinito, ad es. null string s dovrebbe diventare String.Empty, null int? s dovrebbe diventare 0, null bool? s deve essere false e così via.

NullValueHandling non è utile, dal momento che non desidero annullare i valori di Ignore, ma nemmeno io voglio Include (Hmm, nuova funzione?).

Così ho deciso di implementare una custom JsonConverter.
Mentre l'implementazione stessa era un gioco da ragazzi, sfortunatamente questo non funzionava ancora - CanConvert() non viene mai chiamato per una proprietà che ha un valore nullo, e quindi non viene chiamato neanche WriteJson(). Apparentemente i valori nulli vengono automaticamente serializzati direttamente in null, senza la pipeline personalizzata.

Ad esempio, qui è un esempio di un convertitore personalizzato per stringhe null:

public class StringConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return typeof(string).IsAssignableFrom(objectType); 
    } 

    ... 
    public override void WriteJson(JsonWriter writer, 
       object value, 
       JsonSerializer serializer) 
    { 
     string strValue = value as string; 

     if (strValue == null) 
     { 
      writer.WriteValue(String.Empty); 
     } 
     else 
     { 
      writer.WriteValue(strValue); 
     } 
    } 
} 

Entrando attraverso questo nel debugger, ho notato che nessuno di questi metodi sono chiamati oggetti che hanno un valore nullo.

Scavando nel codice sorgente di JSON.NET, ho scoperto che (a quanto pare, non sono entrato in profondità) c'è un caso speciale che verifica i valori nulli e chiama esplicitamente .WriteNull().

Per quello che vale, ho provato l'attuazione di un personalizzato JsonTextWriter e ignorando l'implementazione predefinita .WriteNull() ...

public class NullJsonWriter : JsonTextWriter 
{ 
    ... 
    public override void WriteNull() 
    { 
     this.WriteValue(String.Empty); 
    } 
} 

Tuttavia, questo non può funzionare bene, in quanto il metodo WriteNull() non sa nulla del sottostante tipo di dati. Così sicuro, posso produrre "" per qualsiasi null, ma che non funziona bene per es. int, bool, ecc.

Quindi, la mia domanda, a meno di convertire manualmente l'intera struttura dati, può esserci qualche soluzione o soluzione?

+0

Sto indovinando che il metodo 'WriteNull()' è chiamato internamente all'interno del processo di serializzazione JSON e non è possibile determinare quale valore si sta attualmente serializzando? –

+0

Il metodo WriteNull viene chiamato da JsonSerializer quando la proprietà ha un valore nullo. Per essere precisi, il valore che sto serializzando è sempre nullo :), ma sì sembra che non ci sia modo di conoscere il tipo di dati sottostante per il quale il null viene scritto. – AviD

+0

Qual è il punto di utilizzo dei tipi nullable se si intende ignorare null come stato valido dell'oggetto? –

risposta

25

Ok, penso di aver trovato una soluzione (la mia prima soluzione non era affatto corretta, ma poi ero di nuovo in treno). È necessario creare un risolutore di contratto speciale e un ValueProvider personalizzato per i tipi Nullable. Considerate questo:

public class NullableValueProvider : IValueProvider 
{ 
    private readonly object _defaultValue; 
    private readonly IValueProvider _underlyingValueProvider; 


    public NullableValueProvider(MemberInfo memberInfo, Type underlyingType) 
    { 
     _underlyingValueProvider = new DynamicValueProvider(memberInfo); 
     _defaultValue = Activator.CreateInstance(underlyingType); 
    } 

    public void SetValue(object target, object value) 
    { 
     _underlyingValueProvider.SetValue(target, value); 
    } 

    public object GetValue(object target) 
    { 
     return _underlyingValueProvider.GetValue(target) ?? _defaultValue; 
    } 
} 

public class SpecialContractResolver : DefaultContractResolver 
{ 
    protected override IValueProvider CreateMemberValueProvider(MemberInfo member) 
    { 
     if(member.MemberType == MemberTypes.Property) 
     { 
      var pi = (PropertyInfo) member; 
      if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof (Nullable<>)) 
      { 
       return new NullableValueProvider(member, pi.PropertyType.GetGenericArguments().First()); 
      } 
     } 
     else if(member.MemberType == MemberTypes.Field) 
     { 
      var fi = (FieldInfo) member; 
      if(fi.FieldType.IsGenericType && fi.FieldType.GetGenericTypeDefinition() == typeof(Nullable<>)) 
       return new NullableValueProvider(member, fi.FieldType.GetGenericArguments().First()); 
     } 

     return base.CreateMemberValueProvider(member); 
    } 
} 

Poi ho provato con:

class Foo 
{ 
    public int? Int { get; set; } 
    public bool? Boolean { get; set; } 
    public int? IntField; 
} 

e la seguente caso:

[TestFixture] 
public class Tests 
{ 
    [Test] 
    public void Test() 
    { 
     var foo = new Foo(); 

     var settings = new JsonSerializerSettings { ContractResolver = new SpecialContractResolver() }; 

     Assert.AreEqual(
      JsonConvert.SerializeObject(foo, Formatting.None, settings), 
      "{\"IntField\":0,\"Int\":0,\"Boolean\":false}"); 
    } 
} 

Speriamo che questo aiuta un po '...

Modifica – migliore identificazione del tipo Nullable<>

Modifica – Aggiunto il supporto per i campi così come le proprietà, anche piggy-backing in cima alla normale DynamicValueProvider per fare la maggior parte del lavoro, con aggiornata test

+0

Credo che si possa fare Type.IsValueType se lo si desidera –

+0

@IanJacobs L'ho capito usando 'GetGenericTypeDefinition() == typeof (Nullable <>)'. –

+0

Wow, questo è ... un po 'più complicato di quanto mi aspettassi, specialmente per qualcosa di così banale ... Comunque ci vorrà un po 'per collegarlo e controllare, ma sembra buono! Grazie, nel frattempo ... – AviD

Problemi correlati