46 votes

Sérialisation des valeurs null dans JSON.NET

Lors de la sérialisation de données arbitraires via JSON.NET toute la propriété est null est écrit dans le JSON comme

"propertyName" : null

C'est correct, bien sûr.

Toutefois, j'ai une exigence de traduire automatiquement toutes les valeurs null dans le vide par défaut de la valeur, par exemple, null strings doit devenir String.Empty, null int?s doit devenir 0, null bool?s doit être false, et ainsi de suite.

NullValueHandling n'est pas utile, étant donné que je ne veux pas Ignore les valeurs null, mais je ne peux m'souhaitez Include eux (Hmm, nouvelle fonctionnalité?).

Je me suis donc tourné à la mise en œuvre d'une coutume JsonConverter.
Alors que la mise en œuvre elle-même était un jeu d'enfant, malheureusement cela na pas encore de travail - CanConvert() n'est jamais appelé pour un bien qui a une valeur nulle, et par conséquent WriteJson() n'est pas appelé. Apparemment, les valeurs null sont automatiquement sérialisé directement dans null, sans le pipeline personnalisé.

Pour exemple, voici un exemple d'un convertisseur personnalisé pour des chaînes vides:

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

Pas à pas dans cette, dans le débogueur, j'ai constaté qu'aucune de ces méthodes sont appelées pour les propriétés qui ont une valeur null.

Fouiller dans JSON.NET s'code source, j'ai trouvé que (apparemment, je n'ai pas aller dans beaucoup de profondeur) il existe un cas particulier de la vérification pour les valeurs null, et explicitement en appelant .WriteNull().

Pour ce que ça vaut, j'ai essayé de mettre en œuvre une coutume JsonTextWriter et en remplaçant la valeur par défaut .WriteNull() mise en œuvre...

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

Cependant, cela ne peut pas bien fonctionner, puisque l' WriteNull() méthode ne sait rien sur le type de données sous-jacente. Alors bien sûr, je peux de sortie "" pour toute nulle, mais qui ne fonctionne pas bien, par exemple de type int, bool, etc.

Donc, ma question de la conversion de l'ensemble de la structure de données manuellement, est-il une solution pour cela?

26voto

32bitkid Points 11851

D'accord, je pense que je suis venu avec une solution de (ma première solution n'est pas droit du tout, mais alors encore une fois j'étais dans le train). Vous avez besoin de créer un contrat spécial de résolution et une coutume ValueProvider pour les types Nullables. Réfléchissez à ceci:

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

Puis je l'ai testé à l'aide de:

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

Et le cas suivant:

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

Espérons que cela aide un peu...

Edit – une Meilleure identification de l' Nullable<> type

Edit – Ajout du support pour les champs ainsi que les propriétés, aussi piggy-backing sur le dessus de la normale DynamicValueProvider à faire la plupart du travail, avec la mise à jour de test

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X