JamesNK / Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for .NET
https://www.newtonsoft.com/json
MIT License
10.73k stars 3.25k forks source link

Issue with serialization of null values using custom converter #1639

Open btshft opened 6 years ago

btshft commented 6 years ago

Intro

Area: Converters, Serialization Issue: Methods JsonConverter.CanConvert and JsonConverter.WriteJson are not called during serialization if source value is null. Description: I need to convert the C# object to JSON string in such way that for every DateTime property of source object the result JSON will contain a duplicate property with name of source property plus suffix 'Msk' and it will contain a Moscow DateTime. If the source property value is null, then I want the resulting value to be null too. However now if the value is null the methods CanConvert and WriteJson are not called, and accordingly the new property is not created in result JSON. Library version: 11.0.1 Platform: .NET Core 2.1.4 (also reproduced at .NET Framework 4.6.1)

Source type model

public class Flight
{
    public DateTime? ArrivalTime { get; set; }
    public DateTime? DepartureTime { get; set; }
}

JsonConverter

public class AddMskDateTimeConverter : JsonConverter
{
    /// <inheritdoc />
    public override bool CanRead { get; } = false;

    /// <inheritdoc />
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var propertyName = writer.Path.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries).Last();
        var mskProperty = new JProperty($"{propertyName}Msk", null);

        if (value == null)
        {
            writer.WriteNull();
        }
        else
        {
            var dt = (DateTime) value;

            writer.WriteValue(dt);
            mskProperty.Value = dt.AddHours(3); // Assume time UTC, simplified for example
        }

        mskProperty.WriteTo(writer);
    }

    /// <inheritdoc />
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotSupportedException("Only serialization is supported");
    }

    /// <inheritdoc />
    public override bool CanConvert(Type objectType)
    {
        return typeof(DateTime?).IsAssignableFrom(objectType);
    }
}

Serialization

class Program
{
    static void Main(string[] args)
    {
        var flight = new Flight
        {
            DepartureTime = DateTime.UtcNow
        };

        var settings = new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Include,
            DefaultValueHandling = DefaultValueHandling.Populate
        };

        settings.Converters.Add(new AddMskDateTimeConverter());

        var json = JsonConvert.SerializeObject(flight, settings);
    }
}

Actual JSON after serialization

{
    "ArrivalTime": null,
    "DepartureTime": "2018-03-09T20:32:01.763685Z",
    "DepartureTimeMsk": "2018-03-09T23:32:01.763685Z"
}

Expected JSON after serialization

{
    "ArrivalTime": null,
        "ArrivalTimeMsk": null,   // <-- This is missing
    "DepartureTime": "2018-03-09T20:32:01.763685Z",
    "DepartureTimeMsk": "2018-03-09T23:32:01.763685Z"
}
benbryant0 commented 4 years ago

This is still an issue for me with .NET Framework 4.8 & version 12.0.3

I found an example of the issue written by someone else: https://dotnetfiddle.net/5PLvsk

jcumming3 commented 3 years ago

I believe the Json library to write the values calls if(value == null){writeNullValue(value);} before it picks up the custom converter. Unless Netwonsoft has changed that recently your custom converter will never write a null value. I worked around this by creating a custom resolver and overriding the IValueProvider methods and set "default" custom values there.
Things may have changed or i may be missing something but as far as I can see that is the issue and the way around it.

LBalsa commented 1 year ago

I figure this is still not supported? In System.Text.Json JsonConverter has an overrible bool HandleNull property which allows this behaviour. I can't find a similar thing on Newtonsoft. A workaround would be to write a converter for the class containing the property, but it is quite long.

AlexandrSargsyan commented 4 months ago

Workaround is to create new data type which will be wrapper over original data and in WriteJson handle the case when inner value is null