35 votos

Serializar null en JSON.NET

Cuando serialización de datos arbitrarios a través de JSON.NET cualquier propiedad que es null se escribe en el JSON como

"propertyName" : null

Esto es correcto, por supuesto.

Sin embargo tengo un requisito para traducir automáticamente todos los valores nulos en el valor predeterminado valor vacío, por ejemplo, null strings debe convertirse String.Empty, null int?s debe convertirse 0, null bool?s debe ser false, y así sucesivamente.

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

Así que me volví a la implementación de un personalizados JsonConverter.
Mientras que la aplicación en sí, era una brisa, lamentablemente, esto todavía no funciona - CanConvert() nunca se llama a una propiedad que tiene un valor nulo, y por lo tanto, WriteJson() no es llamado. Al parecer, los valores nulos son automáticamente serializado directamente en null, sin la canalización personalizada.

Por ejemplo, aquí se muestra un ejemplo de un convertidor personalizado 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);
        }
    }
}

Los pasos a través de este en el depurador, me señaló que ninguno de estos métodos son llamados por las propiedades que tienen un valor nulo.

Profundizar en JSON.NETO del código fuente, me encontré con que, al parecer, yo no tenía que ir a un montón de profundidad) no es un caso especial de la comprobación de valores nulos, y explícitamente llamando .WriteNull().

Para lo que vale, me hizo probar la implementación de un personalizados JsonTextWriter y reemplazar el valor predeterminado .WriteNull() de ejecución...

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

Sin embargo, esto puede no funcionar bien, ya que el WriteNull() método no sabe nada acerca del tipo de datos. Tan seguro, puedo salida "" para cualquier nulo, pero que no funciona bien, por ejemplo, int, bool, etc.

Así que mi pregunta - corto de la conversión de la totalidad de la estructura de datos manualmente, existe alguna solución o una solución para esto?

17voto

32bitkid Puntos 11851

Bueno, creo que he dado con una solución de (mi primera solución no estaba bien en absoluto, pero, de nuevo estaba en el tren). Usted necesita para crear un contrato especial de resolución y una costumbre ValueProvider para tipos que aceptan valores null. 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 probado usando:

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

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

Esperamos que esto les ayude un poco...

Edición – Mejor identificación de la a Nullable<> tipo

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

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by: