JamesNK / Newtonsoft.Json

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

Nullable DateTime in Dictionary can't use custom converter to convert into empty string #2945

Open kamyaw010 opened 3 months ago

kamyaw010 commented 3 months ago

I have some classes must be stored in a dictionary, but the code can not be uploaded, so I simplify the code and change it to below acts like the actual behavior.

But the Nullable\<DateTime> can not trigger the converter and can't be serialize into an empty string, always serialize into null value, but actually I need it is an empty string so the front-end can accept it (front-end is a third-party library that can not be changed and some fields must have unless an empty string than null )

Source/destination types

JsonConvertTestClass Code ( have tried using [JsonConverter] attribute in DateTime? field ):

public record JsonConvertTestClass
{
    public DateTime? GTime { get; set; }
    public ParentClass Parent { get; set; } = new();
}

public record ParentClass
{
    public DateTime? PTime { get; set; }
    public ChildClass Child { get; set; } = new();
}

public record ChildClass
{
    public DateTime? CTime { get; set; }
}

Custom Converter Code (no matter use JsonConverter or JsonConverter<DateTime?>):

public class DateTimeConverter : JsonConverter
{
    public override bool CanWrite => true;

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(DateTime?) || objectType == typeof(DateTime);
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        if (value is null)
        {
            writer.WriteValue(string.Empty);
        }
        else
        {
            writer.WriteValue(value);
        }
    }
}

Serialization Code:

var dict = new Dictionary<string, JsonConvertTestClass>();
for (var i = 0; i < 2; i++)
{
    dict.Add(i.ToString(), new JsonConvertTestClass());
}

Console.WriteLine(JsonConvert.SerializeObject(dict, Formatting.Indented, new JsonSerializerSettings { Converters = { new DateTimeConverter() } }));

Source/destination JSON

{
  "0": {
    "GTime": null,
    "Parent": {
      "PTime": null,
      "Child": {
        "CTime": null
      }
    }
  },
  "1": {
    "GTime": null,
    "Parent": {
      "PTime": null,
      "Child": {
        "CTime": null
      }
    }
  }
}

Expected behavior

{
  "0": {
    "GTime": "",
    "Parent": {
      "PTime": "",
      "Child": {
        "CTime": ""
      }
    }
  },
  "1": {
    "GTime": "",
    "Parent": {
      "PTime": "",
      "Child": {
        "CTime": ""
      }
    }
  }
}

Actual behavior

All DateTime are null

Steps to reproduce

// Even use [JsonConverter] attribute doesn't work
Console.WriteLine(JsonConvert.SerializeObject(dict, Formatting.Indented, new JsonSerializerSettings { Converters = { new DateTimeConverter() } }));
elgonzo commented 3 months ago

The behavior you see is pretty much hard-coded: https://github.com/JamesNK/Newtonsoft.Json/blob/2eaa475f8853f2b01ac5591421dcabd7a44f79ce/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs#L154-L160

As a (past) user of the library myself (only still using it in old legacy projects i didn't migrate to modern .NET), i have to admit that i can't muster much hope for this situation to be improved. That said, feel free to look at Newtonsoft.Json's development activities over the last few years and form your own opinion about it.

A work-around for this limitation is to write a converter that deals with the dictionary itself -- this way, this dictionary converter would then be able to iterate over the dictionary, deal with Nullable\<T> values itself directly (thus bypassing hard-coded null checks in the serializer) and write/serialize the dictionary data to the JsonWriter in whatever way you need. If you feel this being too much work or your real situation is more complex and involving not just dictionaries with values from a limited number of relatively simple types, i would strongly suggest you contemplate switching to STJ if there are no roadblocks preventing you from doing that...

kamyaw010 commented 3 months ago

The behavior you see is pretty much hard-coded:

https://github.com/JamesNK/Newtonsoft.Json/blob/2eaa475f8853f2b01ac5591421dcabd7a44f79ce/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs#L154-L160

As a (past) user of the library myself (only still using it in old legacy projects i didn't migrate to modern .NET), i have to admit that i can't muster much hope for this situation to be improved. That said, feel free to look at Newtonsoft.Json's development activities over the last few years and form your own opinion about it.

A work-around for this limitation is to write a converter that deals with the dictionary itself -- this way, this dictionary converter would then be able to iterate over the dictionary, deal with Nullable values itself directly (thus bypassing hard-coded null checks in the serializer) and write/serialize the dictionary data to the JsonWriter in whatever way you need. If you feel this being too much work or your real situation is more complex and involving not just simple dictionaries, i would strongly suggest you contemplate switching to STJ if there are no roadblocks preventing you from doing that...

For some reasons, can't switch back to STJ :( Seems can just hold a converter for the dictionary otherwise change this field into string? and handle it myself Thanks.

elgonzo commented 3 months ago

For some reasons, can't switch back to STJ :(

That's unfortunate :-( because this would be easy-peasy to do in STJ https://dotnetfiddle.net/RDmNxD