Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The JSON value could not be converted - Model with nullable DateTime #434

Closed
troncomputers opened this issue Dec 2, 2019 · 7 comments
Closed

Comments

@troncomputers
Copy link

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

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Dec 2, 2019
@Clockwork-Muse
Copy link
Contributor

... 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...

@troncomputers
Copy link
Author

@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?

@Clockwork-Muse
Copy link
Contributor

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.

@layomia
Copy link
Contributor

layomia commented Dec 3, 2019

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:

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)

@layomia layomia added this to the 5.0 milestone Dec 3, 2019
@layomia layomia removed the untriaged New issue has not been triaged by the area owner label Dec 3, 2019
@layomia layomia modified the milestones: 5.0, Future Dec 4, 2019
@ylibrach
Copy link

ylibrach commented Jan 8, 2020

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?

@a2kan
Copy link

a2kan commented Apr 27, 2021

return reader.GetString() == "" ? null : reader.GetDateTime();

In my case this part didn't work for me (if you have a datetime string like this : 2021-04-27 00:00:00)

I changed to
return reader.GetString() == "" ? null : DateTime.Parse(reader.GetString());

@eiriktsarpalis
Copy link
Member

Closing as by-design behavior. A workaround has been provided in #434 (comment)

@ghost ghost locked as resolved and limited conversation to collaborators Nov 21, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants