Hi!
My API is giving me an error on post requests. The posted data contains an empty string in property that is giving me this error.
Passed part of data:
agreementStartDate: "2019-12-09T00:00:00.000Z"
agreementEndDate: ""
Error message:
"The JSON value could not be converted to System.Nullable1[System.DateTime]. Path: $.agreementEndDate | LineNumber: 0 | BytePositionInLine: 222."
Model:
public class Manager
{
//...
[Required]
public DateTime AgreementStartDate {get;set;}
public DateTime? AgreementEndDate {get;set;}
}
Should empty string be considered as NULL
?
.NET Core 3.0
... normally the element should be left out in cases like this:
{
"agreementStartDate": "2019-12-09T00:00:00.000Z"
}
... so I'd normally start with squawking at whoever was calling the API incorrectly (Or let them squawk at themselves as it were, since I presume you return a 400 for a failed deserialize).
We can't blanket convert empty to null, since whether empty is valid is up to the individual type deserializers. An attribute like [TreatEmptyAsNull]
may be okay, although since callers should normally leave the element off...
@Clockwork-Muse
... normally the element should be left out
You are absolutely right. This empty string (probably) came from Angular FormGroup()
that is initialized like that:
frmManager = new FormGroup({
agreementStartDate: new FormControl('', Validators.Required),
agreementEndDate: new FormControl('')
But, I think if date is an empty string, it should be treated as NULL
or, like you wrote, a little attribute can do the job. What would be the use of a date represented as an empty string?
The problem is that a blank date (or most other objects) would normally be considered invalid on the grounds that, hey, the key was specified, but didn't have a value. Usually you want to stare at your caller because they're probably doing something wrong, which might include not initializing or setting a value they think they are.
I don't think we should allow an empty string to mean null
for DateTime by default, because the empty string is not null
. Also, other users might depend on the empty string being invalid for these types.
If we do, we'd have to consider the behavior for other types with string format representations: Guid, Uri, TimeSpan etc.
A workaround for your scenario is to use a converter:
```c#
using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Program
{
public static void Main()
{
string json = @"{""agreementStartDate"":""2019-12-09T00:00:00.000Z"",""agreementEndDate"":""""}";
JsonSerializerOptions options = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
options.Converters.Add(new CustomJsonConverterForNullableDateTime(options));
Manager manager = JsonSerializer.Deserialize<Manager>(json, options);
Console.WriteLine(manager.AgreementStartDate);
Console.WriteLine(manager.AgreementEndDate == null);
}
private class Manager
{
//...
[Required]
public DateTime AgreementStartDate { get; set; }
public DateTime? AgreementEndDate { get; set; }
}
private class CustomJsonConverterForNullableDateTime : JsonConverter<DateTime?>
{
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Console.WriteLine("Reading");
Debug.Assert(typeToConvert == typeof(DateTime?));
return reader.GetString() == "" ? null : reader.GetDateTime();
}
// This method will be ignored on serialization, and the default typeof(DateTime) converter is used instead.
// This is a bug: https://github.com/dotnet/corefx/issues/41070#issuecomment-560949493
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
{
Console.WriteLine("Here - writing");
if (!value.HasValue)
{
writer.WriteStringValue("");
}
else
{
writer.WriteStringValue(value.Value);
}
}
}
}
```
If the goal is to ignore empty string values entirely, a future solution with default value handling semantics could be to specify the empty string as a default value for the property (with an attribute), then ignore if null or default (building atop the proposals in https://github.com/dotnet/corefx/issues/40600, https://github.com/dotnet/corefx/issues/38878)
I don't think we should allow an empty string to mean null for DateTime by default, because the empty string is not null. Also, other users might depend on the empty string being invalid for these types.
This differs from Newtonsoft's default behavior and would be a breaking change for migrations from Newtonsoft to System.Text.Json. Is that intentional?
Most helpful comment
This differs from Newtonsoft's default behavior and would be a breaking change for migrations from Newtonsoft to System.Text.Json. Is that intentional?