2012-01-12 20 views
45

Kiedy szeregowania dowolnych danych poprzez Json.NET, jakiegokolwiek mienia, które jest NULL są zapisywane w formacie JSON jakoSzeregowania wartości null w Json.NET

"propertyName": null

To jest prawidłowe, oczywiście.

Mam jednak wymóg automatycznego tłumaczenia wszystkich wartości null na domyślną pustą wartość, np. null string s powinno stać się String.Empty, null int? s powinno stać się 0, null bool? s powinno być false, i tak dalej.

NullValueHandling nie jest pomocne, ponieważ nie chcę Ignore wartości null, ale nie chcę też Include je (Hmm, nowa funkcja?).

Więc zwróciłem się do realizacji niestandardowego JsonConverter.
Chociaż sama implementacja była prosta, niestety to nadal nie działało - CanConvert() nigdy nie jest wywoływana dla właściwości, która ma wartość pustą, a zatem WriteJson() również nie jest wywoływana. Najwyraźniej wartości null są automatycznie szeregowane bezpośrednio do null, bez niestandardowego potoku.

Na przykład, oto próbka niestandardowych konwerter zerowych strun:

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

Krocząc przez to w debugera, ja zauważyć, że żadna z tych metod są powołani do właściwości, które mają wartość null.

Po przejściu do kodu źródłowego JSON.NET odkryłem, że (najwyraźniej nie wdałem się w głębię) istnieje specjalny przypadek sprawdzania wartości zerowych i jednoznacznie nazywa się .WriteNull().

Na co warto, Próbowałem realizacji niestandardowych JsonTextWriter i przesłanianie domyślna implementacja .WriteNull() ...

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

Jednak to nie może działać dobrze, ponieważ metoda WriteNull() wie nic na temat instrumentu bazowego typ danych. Tak więc, mogę wypisać "" dla dowolnej wartości null, ale to nie działa dobrze na przykład int, bool, itp.

Tak, moje pytanie - brak konwersji całej struktury danych ręcznie, czy istnieje jakieś rozwiązanie lub obejście tego?

+0

Zgaduję, że metoda 'WriteNull()' jest wywoływana wewnętrznie w procesie serializacji JSON i nie możesz określić, którą wartość serializujesz? –

+0

Metoda WriteNull jest wywoływana przez JsonSerializer, gdy właściwość ma wartość pustą. Aby być dokładnym, wartość, którą serializuję, jest zawsze zerowa :), ale tak, wydaje się, że nie ma możliwości poznania podstawowego typu danych, dla którego zapisywana jest wartość pusta. – AviD

+0

Jaki jest sens używania typów zerowujących, jeśli po prostu zignorujesz wartość null jako prawidłowy stan obiektu? –

Odpowiedz

25

Okej, wydaje mi się, że znalazłem rozwiązanie (moje pierwsze rozwiązanie w ogóle nie pasowało, ale znowu byłem w pociągu). Musisz utworzyć specjalny resolver umowy i niestandardowy ValueProvider dla typów Nullable. Rozważ to:

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

Potem przetestowane przy użyciu:

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

oraz następujące sprawy:

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

Mam nadzieję, że to pomoże trochę ...

Edit – Lepsza identyfikacja typu Nullable<>

Edit – Dodano wsparcie dla pól, jak również właściwości, także świnką-podkład na szczycie normalnej DynamicValueProvider zrobić większość prac, z zaktualizowane test

+0

Wierzę, że można zrobić Type.IsValueType, jeśli chcesz. –

+0

@IanJacobs Stwierdziłem to przy użyciu 'GetGenericTypeDefinition() == typeof (Nullable <>)'. –

+0

Wow, to jest ... trochę bardziej skomplikowane, niż się spodziewałem. Zwłaszcza na coś tak banalnego ... W każdym razie to zajmie mi chwilę, żeby to podłączyć i sprawdzić, ale wygląda dobrze! Dzięki, w międzyczasie ... – AviD

Powiązane problemy