6

I am trying to perform custom serialisation, all the happy path code works but the null value path is not behaving as I'd like.

I have set the serializer settings to NullValueHandling.Ignore and other parts of my object graph that are null (and don't use my custom serialization) have the null values removed. It looks like the Newtonsoft serializer writes to a string builder so we should be able to 'rewind' any written json tokens but I don't see how to not write anything.

Doing nothing and just returning causes the serializer to throw an exception as the json would be invalid.

Any clues?

public class SpecialConvertor : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null || (int)value == 0)
        {
            if (serializer.NullValueHandling == NullValueHandling.Ignore)
            {
                //how to make this work?
            }
            else
            {
                writer.WriteNull();
            }
            return;
        }
        // the rest of WriteJson
    }
    // the rest of SpecialConvertor
}
Adam Straughan
  • 2,766
  • 3
  • 20
  • 27
  • 1
    I can't reproduce this problem. 1) `NullValueHandling` is for object references. Since your property is an integer, you probably want [`DefaultValueHandling = DefaultValueHandling.Ignore`](http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_DefaultValueHandling.htm). 2) If the value is null, `JsonConverter.WriteJson()` is simply not called, and Json.NET itself writes the null name & value. Maybe you have some higher level converter that is failing to check `serializer.NullValueHandling`? See https://dotnetfiddle.net/dWfJ0o for an example of what might be wrong. – dbc Oct 20 '15 at 19:16
  • Also, it's possible to serialize directly to a stream, for instance a [`GZipStream`](https://stackoverflow.com/questions/32943899/can-i-decompress-and-deserialize-a-file-using-streams), for which rewinding isn't possible – dbc Oct 20 '15 at 19:30
  • 1
    @dbc good work, not sure how I missed that. Long day etc. Null check probably from when it was a nullable enum, or lack of trust of the serializer. The call was using value as the default (zero) value, not null. The null i was seeing was mine, that I (incorrectly) wrote out. I'll read the dotnetfiddle in more detail later. Thanks. (Add as an answer if you want credit) – Adam Straughan Oct 20 '15 at 21:48

1 Answers1

6

NullValueHandling is for object references. In your example, your value is an integer. To omit integer properties with default values, use the setting DefaultValueHandling = DefaultValueHandling.Ignore.

The null check in WriteJson() should not be necessary because Json.NET never calls the converter with a null value. Instead, it writes the name & null value itself -- or not, if NullValueHandling == NullValueHandling.Ignore. So checking for null and rewinding should never be required.

A null value for an object property might still get written when null value handling or default value handling are Ignore if one of your converters writes it explicitly in WriteJson. To prevent that, you can check the settings and skip nulls like so:

public class MyClassConverter : JsonConverter
{
    const string Prefix = "My Value Is: ";

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var myClass = (MyClass)value;
        writer.WriteStartObject();
        if (myClass.StringValue != null 
            || (serializer.NullValueHandling != NullValueHandling.Ignore 
                && (serializer.DefaultValueHandling & DefaultValueHandling.Ignore) != DefaultValueHandling.Ignore))
        {
            writer.WritePropertyName("StringValue");
            if (myClass.StringValue == null)
                writer.WriteNull();
            else
                serializer.Serialize(writer, Prefix + myClass.StringValue);
        }
        writer.WriteEndObject();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var s = (string)JValue.Load(reader);
        if (s.StartsWith(Prefix))
            s = s.Substring(Prefix.Length);
        return s;
    }

    public override bool CanConvert(Type objectType) { return objectType == typeof(MyClass); }
}

[JsonConverter(typeof(MyClassConverter))]
public class MyClass
{
    public string StringValue { get; set; }
}
dbc
  • 104,963
  • 20
  • 228
  • 340