1

When I use Postman to test my API with a PUT request, I get this error:

"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-fe50a5f13435f11ef5d27de5f91d3c45-47c1ee82a70305a9-00",
"errors": {
    "$.extractionDate": [
        "The JSON value could not be converted to System.Nullable`1[System.DateTime]. Path: $.extractionDate | LineNumber: 0 | BytePositionInLine: 704."
    ]
}

I can see that an empty string that looks like this is being passed to the API:

"extractionDate":""

In my model, I have the ExtractionDate property set as nullable like this:

public DateTime? ExtractionDate { get; set; }

Due to things beyond my control, the old system that is using this API can't pass nulls, it can only pass a blank string for any values that are null.

Is there something else I need to do in order for the JSON to be valid?

Thanks!

SkyeBoniwell
  • 6,345
  • 12
  • 81
  • 185

2 Answers2

4

Well, assuming that you have control of the API and the models on that end, you could write a custom JsonConverter<DateTime?> that handles empty strings by returning null.

A simple implementation of the JsonConverter<DateTime?> might look something like this...

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

public class NullableDateTimeConverter : JsonConverter<DateTime?>
{
    public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var @string = reader.GetString();
        if (string.IsNullOrWhiteSpace(@string))
        {
            return null;
        }
        return DateTime.Parse(@string);
    }

    public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
    {
        if (value != null)
        {
            writer.WriteStringValue(value.Value.ToString("o"));
        }
    }
}

Then, you can tell your model to use it with a JsonConverterAttribute.

using System;
using System.Test.Json.Serialization;

public class TheModel
{
    [JsonConverter(typeof(NullableDateTimeConverter))]
    public DateTime? ExtractionDate { get; set; }
}
Joshua Robinson
  • 3,399
  • 7
  • 22
  • Oh thank you, wow that looks very powerful tool! Could you make that something like generics? For example pass in a type, like DateTime, or Guid, or String, etc? Would that just be changing this part? `JsonConverter`? – SkyeBoniwell Apr 29 '22 at 15:17
  • 1
    Good question. I'm afraid I'm not sure. You could maybe do some runtime type checking on `typeToConvert` in `Read` or `value` in `Write` to figure out how to serialize or deserailize? I don't know enough about `System.Text.Json` to say for sure if there is a better way. Sorry. – Joshua Robinson Apr 29 '22 at 16:43
3

You can make an extension method and use it everywhere

    public static DateTime? ParseDateOrDefault(this string date)
    {

        if (string.IsNullOrEmpty(date))
            return null;

        return DateTime.Parse(date);
    }

Then in your api make another public string DateTime variable that will accept the datetime passed in. Make your current DateTime variable internal with getters and setters that have logic with the new extension method.

internal DateTime? MyDate 
{
  get
  {
    return MyDateStringVariable.ParseDateOrDefault();
  }
  ...
}
Train
  • 3,420
  • 2
  • 29
  • 59
  • Would that `internal DateTime? MyDate` go in my models? Like would I replace `public DateTime? ExtractionDate { get; set; }` with that? Thanks! – SkyeBoniwell Apr 29 '22 at 11:56
  • Hi, I'm not quite sure how to use this extension. I made a static class called EmptyToNull in an Extensions folder and put your `ParseDateOrDefault` method in there. In my model, I made sure to add a `using` directive for the Extensions folder. I then turned this: `public DateTime? ExtractionDate { get; set; } ` into this: `public DateTime? ExtractionDate { get { return ExtractionDate.ParseDateOrDefault(); }` but it can't find the `ParseDateOrDefault()` extension method – SkyeBoniwell Apr 29 '22 at 13:58
  • 1
    Sorry for the late reply. Yes it would go in your api model. If the solution below didn't work please post your code. It may be the access modifiers preventing you from calling it. – Train Apr 30 '22 at 15:53