2012-01-12 9 views
45

Cuando seria datos arbitrarios a través de JSON.NET, cualquier propiedad que es nulo se escribe en el JSON comode números de serie nula en JSON.NET

"nombrePropiedad": null

Esto es correcto, por supuesto.

Sin embargo, tengo la obligación de traducir automáticamente todos los nulos al valor vacío predeterminado, p. nulo string s debe convertirse en String.Empty, nulo int? s debe convertirse en 0, nulo bool? s debe ser false, y así sucesivamente.

NullValueHandling no es muy útil, ya que no quiero Ignore nulos, pero tampoco quiero Include ellos (Hmm, nueva característica?).

Así que recurrí a la implementación de un JsonConverter personalizado.
Si bien la implementación en sí misma fue muy sencilla, desafortunadamente esto aún no funcionó: CanConvert() nunca se invoca para una propiedad que tiene un valor nulo y, por lo tanto, tampoco se llama a WriteJson(). Aparentemente, los valores nulos se serializan automáticamente directamente en null, sin la interconexión personalizada.

Por ejemplo, aquí es una muestra de un convertidor de medida para cadenas nulas:

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

Pasando a través de este en el depurador, he observado que ninguno de estos métodos son llamados para las propiedades que tienen un valor nulo.

Al profundizar en el código fuente de JSON.NET, descubrí que (al parecer, no profundicé mucho) hay un caso especial que comprueba si hay nulos y se llama explícitamente al .WriteNull().

Por lo que vale la pena, Yo probé la implementación de una costumbre JsonTextWriter y anulando la implementación predeterminada .WriteNull() ...

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

Sin embargo, esto no puede funcionar bien, ya que el método WriteNull() no sabe nada sobre el subyacente tipo de datos. Así que estoy seguro, puedo sacar "" para cualquier nulo, pero eso no funciona bien para, p. int, bool, etc.

Por lo tanto, mi pregunta es que, a menos que se convierta manualmente toda la estructura de datos, ¿hay alguna solución o solución para esto?

+0

Supongo que el método 'WriteNull()' se llama internamente en el proceso de serialización JSON y no puede determinar qué valor está serializando actualmente? –

+0

JsonSerializer llama al método WriteNull cuando la propiedad tiene un valor nulo. Para ser preciso, el valor que estoy serializando siempre es nulo :), pero sí, parece que no hay forma de conocer el tipo de datos subyacente para el que se escribe el nulo. – AviD

+0

¿De qué sirve usar tipos que aceptan nulos si va a ignorar nulo como un estado válido del objeto? –

Respuesta

25

Bueno, creo que he encontrado una solución (mi primera solución no fue para nada, pero de nuevo estaba en el tren). Debe crear una resolución de contrato especial y un ValueProvider personalizado para tipos anulables. Considere esto:

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

Luego he comprobado usando:

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

Y el siguiente 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}"); 
    } 
} 

Esperemos que esto ayuda un poco ...

Editar – mejor identificación del un tipo Nullable<>

Editar – Añadido soporte para los campos, así como propiedades, también piggy-respaldo en la parte superior de la normal de DynamicValueProvider hacer la mayoría del trabajo, con actualizado prueba

+0

Creo que puede hacer Type.IsValueType si lo desea. –

+0

@IanJacobs Lo descubrí usando 'GetGenericTypeDefinition() == typeof (Nullable <>) '. –

+0

Wow, esto es ... un poco más complicado de lo que esperaba. Especialmente para algo tan trivial ... De todos modos tomará un rato para conectar esto y verificar, pero se ve bien! Gracias, mientras tanto ... – AviD

Cuestiones relacionadas